{ "cells": [ { "cell_type": "markdown", "id": "aa81c23a-02bc-4886-b70b-1880209d9353", "metadata": {}, "source": [ "# Visualizing the space of handwritten digits using masspcf" ] }, { "cell_type": "markdown", "id": "4d5c07dc-1f3c-406d-8431-9f4346c6ba6a", "metadata": {}, "source": [ "This notebook serves as an introduction to using the 'masspcf' Python package. \n", "\n", "We will create point cloud data from the MNIST dataset of handwritten digits. From these data we will extract topological invariants (piecewise constant functions: PCFs) and store in a matrix. Using this matrix, we compute pairwise distances between instances in our dataset and finally cluster these using a t-SNE embedding." ] }, { "cell_type": "markdown", "id": "0f93f80d-939d-48f8-8e96-455fb71c71f7", "metadata": {}, "source": [ "# Install and activate prerequisites" ] }, { "cell_type": "code", "execution_count": null, "id": "150d7d5d-37dd-4bba-be4a-6c1f34ddfae0", "metadata": {}, "outputs": [], "source": [ "# For better plotting support in notebook\n", "!pip install ipympl\n", "!pip install tqdm\n", "# For creation of sample dataset\n", "!pip install scikit-learn\n", "!pip install pandas\n", "# For barcode computation\n", "!pip install ripser" ] }, { "cell_type": "code", "execution_count": 2, "id": "6228e713-52d4-4ce9-8bb0-d5dc1f7e3af9", "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "import matplotlib.pyplot as plt\n", "import ipympl\n", "%matplotlib widget\n", "from tqdm import trange, tqdm" ] }, { "cell_type": "markdown", "id": "3c307869-88d2-4d28-9e79-0d89e3fc4bf1", "metadata": {}, "source": [ "# Install masspcf" ] }, { "cell_type": "markdown", "id": "b8686ca7-ebc0-4669-a058-dc80de374484", "metadata": {}, "source": [ "For details, see https://masspcf.readthedocs.io" ] }, { "cell_type": "code", "execution_count": null, "id": "837f3229-8906-4da6-82c9-2013f7977769", "metadata": {}, "outputs": [], "source": [ "# Install masspcf-cpu or, alternatively, masspcf if one or more CUDA GPUs are available\n", "!pip install masspcf-cpu" ] }, { "cell_type": "markdown", "id": "526391cc-dfb1-464e-8331-de76acea44c2", "metadata": {}, "source": [ "# Build example dataset" ] }, { "cell_type": "markdown", "id": "04c894cc-f518-42ea-b6b1-e0726e22752c", "metadata": {}, "source": [ "We will use the MNIST dataset of 28x28 grayscale images of handwritten digits:\n", "\n", "[1] LeCun, Y., Cortes, C., & Burges, CJ. (1998). The MNIST database of handwritten digits" ] }, { "cell_type": "code", "execution_count": 3, "id": "b0a73b13-36ad-4f4d-af4d-775604190d88", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "(70000, 28, 28)\n" ] } ], "source": [ "from sklearn.datasets import fetch_openml\n", "Xin, yin = fetch_openml(\"mnist_784\", version=1, return_X_y=True, as_frame=False)\n", "Xin = Xin.reshape(Xin.shape[0], 28, 28)\n", "print(Xin.shape)" ] }, { "cell_type": "code", "execution_count": 4, "id": "b85f7a4b-de7e-4388-a770-9e38de81c63d", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "(14702, 28, 28)\n" ] } ], "source": [ "use_digits=[1, 8]\n", "mask = np.logical_or.reduce([yin == str(i) for i in use_digits])\n", "X = Xin[mask]\n", "y = yin[mask]\n", "print(X.shape)" ] }, { "cell_type": "code", "execution_count": 5, "id": "eb7ef316-88ae-442f-bc43-6d54d06719af", "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "4f46f5f046a5432181fefdd478beab9e", "version_major": 2, "version_minor": 0 }, "image/png": "iVBORw0KGgoAAAANSUhEUgAAA+gAAAEsCAYAAABQRZlvAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjAsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvlHJYcgAAAAlwSFlzAAAPYQAAD2EBqD+naQAAH/5JREFUeJzt3QmQFOXZB/BelPuWU8JlYjyp0goqoNGCBI8Qr4gxJiZRQiQqGtGqqMQjUT+CUSNGgrcRTaLEI3hiLETQHKCFRg2ilknJkcCCooCKoMJ81fN9EHp6s8Puzuy8M/P7VbXr03TPvPT+2dlnevrtmkwmk4kAAACAkmpR2qcHAAAAYhp0AAAACIAGHQAAAAKgQQcAAIAAaNABAAAgABp0AAAACIAGHQAAAAKgQQcAAIAAaNABAAAgABp0AAAACIAGHQAAAAKgQQcAAIAAaNABAAAgABp0AAAACIAGHQAAAAKgQQcAAIAAaNABAAAgABp0AAAACIAGHQAAAAKgQQcAAIAAaNABAAAgABp0AAAACIAGHQAAAAKgQQcAAIAAaNABAAAgABp0AAAACIAGHQAAAAKgQQcAAIAAaNABAAAgABp0AAAACIAGHQAAAAKgQQcAAIAAaNABAAAgABp0AAAACIAGHQAAAAKgQQcAAIAAaNABAAAgABp0AAAACIAGHQAAAAKgQQcAAIAAaNABAAAgABp0AAAACIAGHQAAAAKgQQcAAIAAaNABAAAgABp0AAAACIAGHQAAAAKgQQcAAIAAaNABAAAgABp0AAAACIAGHQAAAAKgQQcAAIAAaNABAAAgABp0AAAACIAGHQAAAAKgQQcAAIAAaNABAAAgABp0AAAACIAGHQAAAAKgQQcAAIAAaNABAAAgABp0AAAACIAGHQAAAAKgQQcAAIAAaNABAAAgABp0AAAACIAGHQAAAAKgQQcAAIAAaNABAAAgABp0AAAACIAGHQAAAAKgQQcAAIAAaNABAAAgABp0AAAACIAGHQAAAAKgQQcAAIAAaNABAAAgABp0AAAACIAGHQAAAAKgQQcAAIAAaNABAAAgABp0AAAACIAGHQAAAAKgQQcAAIAAaNABAAAgABp0AAAAqLYGfdq0adHAgQOjNm3aREOGDImef/755nx6SJBHQiKPhEQeCYk8EhqZpJhqMplMJmoGv//976Pvfve70c0335wN8vXXXx/df//90RtvvBH17Nmz3n23bNkSrVixIurYsWNUU1PTHMOlBOIovv/++1GfPn2iFi1aBJvHmExWPnmkWjMpj+wIeSQk5fKaLY/VIdPUPGaayUEHHZQZP378tnrz5s2ZPn36ZCZPnpx33+XLl8dvIliqZIm/3yHnMSaT1bPIo6XaMimPFnm0lOsS+mu2PFbXsryReWyWj7h//PHH0QsvvBCNHDly27r43YS4nj9/fmr7TZs2RevXr9+2NNNJfgIRv6sYUh5jMlm95JFqyqQ80lDySEhCe82Wx+rWsZF5bJYG/Z133ok2b94c9erVK7E+rmtra1PbT548OercufO2pX///s0xTAJR7I/8NDSPMZmsXvJINWVSHmkoeSQkob1my2N1q2lkHoOcxX3ixInRunXrti3Lly8v9ZCocjJJSOSRkMgjIZFHQiKPNMbOUTPo3r17tNNOO0WrVq1KrI/r3r17p7Zv3bp1doEQ8hiTSYpFHgmJPBISeSQ0ehqaQ7OcQW/VqlU0ePDgaM6cOYlZDON62LBhzTEE2EYeCYk8EhJ5JCTySGhkkmaRaSYzZszItG7dOjN9+vTM4sWLM+PGjct06dIlU1tbm3ffdevWlXwWPkvzLfH3O+Q8ymR1LfJoqbZMyqNFHi3luoT+mi2P1bWsa2Qem61Bj02dOjXTv3//TKtWrbK3KFiwYMEO7SfM1bU0xw/XpuQxJpPVs8ijpRozKY8WebSU4xL6a7Y8VteyrpF5rIn/EwUuvi1BPPMh1SGeRKNTp05RyGSyesgjoQk9k/JYXeSRkMgjlZDHIGdxBwAAgGqjQQcAAIAAaNABAAAgABp0AAAACMDOpR4AAABQPPfcc09q3dChQxP1N7/5zUT93HPPFX1cQJoz6AAAABAADToAAAAEQIMOAAAAAXANOgAAVLABAwak1g0cODBR/+Y3v0nU++67b2qfTz75pAijA7bnDDoAAAAEQIMOAAAAAdCgAwAAQAA06AAAABAAk8SVSMuWLVPrDj744ET9s5/9LFEfcsghRR8X1aOmpiZR33vvvaltRo0alaj32WefRP2vf/2rSKOD0mnfvn2injdvXqLu06dPap/cn89Lliwp0uioZBMnTkytmzRpUqK++uqrE/VFF11U9HFRfvr165eoDzjggLz77L777ol6553TbYJJ4tgRub8vTpgwIVHvuuuuqX2OPvroRP3www8n6r/+9a95n/fWW29N1GvXro3KkTPoAAAAEAANOgAAAARAgw4AAAABcA16iXTu3Dm1bu7cuYm6trY2Uffu3Tu1T+42sKPatm2bd46DDh06JOqjjjoqUd9+++1FGh00XF3Xhvfo0aPefd57773UuhEjRiTqwYMHJ+o33ngjtc+aNWsaMFL4Px07dkzU55xzTmqbTCZT77Wcb775ZmqfO+64o2BjpDJ+z6xr7qNcDz30UKLetGlTwcdFdfj+97+fqMeOHZt3ny1btiTqY445pt66LhdccEGivvjii1Pb3HLLLVHonEEHAACAAGjQAQAAIAAadAAAAAiAa9ADlnvNuWvQKaQNGzbkvY7xM5/5TIOu54XGGjRoUKL+4Q9/mNpmwIAB9T7GHnvskVrXv3//eve56qqr8t6/taamJlH/+9//Tu3TqlWrep8H6rqv9Jlnnpmoe/XqlfcxVq1alajnz59foNFRSdm68MILG/wY9957b73XBMOO+vrXv97gff72t7/lfa3N50tf+lKiPvnkk1PbuAYdAAAA2CEadAAAAAiABh0AAAACoEEHAACAAJgkLmC5ExNBMU2bNi21bvjw4Yl67733bsYRUU1yJ3YZO3Zsgx9j06ZNqXW//e1v632eiy66KO/jZjKZRD19+vTUNmvWrGnASKlWQ4cOTdSTJ09u8GOcccYZiXrx4sVNHhfl77rrrkvUp5xySsnGAvnUNTHxqFGjEvXq1avzPk7uxJoLFy5M1Pvtt19qn9NOOy1RP/7446lt3n777aiUnEEHAACAAGjQAQAAIAAadAAAAAiAa9ADlnvdY5s2bUo2Firf888/n3ebk046KVFfeOGFqW1WrlxZ0HFRmX76058m6h/96Ed597nrrrvqvUbs2muvTe2Tu83++++fqJ988snUPt27d6/3MR544IG8Y4WBAwem1t1www0Nfpw5c+Yk6nnz5jVpXJS/008/PbWuMfN2QKl8+OGHqXX5rjnv2rVr3n8Lffr0yfvcd9xxR6J+8MEH8/6+29ycQQcAAIAAFKRBf/bZZ6Njjjkm+65FPPP4Qw89lDoTfNlll0W77rpr1LZt22jkyJF1zt4HhSCPhEQeCYk8EhJ5JCTySChaFOpjCvE09nXdpil29dVXZz/WdfPNN0fPPfdc1L59++jII4+MNm7cWIinhwR5JCTySEjkkZDIIyGRRyrqGvSvfOUr2aUu8btN119/fXTJJZdExx13XHbd3Xffnb1vXfzO1Mknn1yIIcA28khI5JGQyCMhkUdCIo9UzSRxb731VlRbW5v9GMhWnTt3joYMGRLNnz9foBvggAMOSK1bsGBBScZSruSxYeKPeG2vVatWifrYY49N7XPLLbcUfVyVoprzGJ952F78ccHtLV26NLXPxRdf3OAJCXffffdE/eMf/zhR9+jRI+/kNbkT2lXq2ZJqzmMxPProo6l1++yzT737rF+/PrXummuuSdQfffRRVA3k8T/GjBmTqKdOnZraJvf1+cUXX0zUX/jCF4o0uuogj4XVr1+/1LrDDjssUS9atChRP/HEEzvUG+WzZcuWRD1z5syo6hr0OMyx+B2m7cX11j/LtWnTpuxS3wsWNFceYzJJMcgjIZFHQiKPhEQeiap9FvfJkydn35XautT1Lgs0J5kkJPJISOSRkMgjIZFHgmzQe/funf26atWqxPq43vpnuSZOnBitW7du27J8+fJiD5Mq0Zg8xmSSYpBHQiKPhEQeCYk8UlEfcd9tt92ywZ0zZ060//77b/t4Rzz74ZlnnlnnPq1bt84ulezTTz9NrYv/4W4vfqdte5/73OeKPq5K15g8Vksm/9ukKPXJveaNhqnmPD7wwAOJ+qijjsp7re5VV12VqM8666x6f2bGrrvuukT91a9+NVG/++67qX0mTZqUqG+66aaoGlRzHoth3333bfDP1BtvvDG1bvbs2VE1Ksc8dujQIVHHM4Ln2mOPPRL1QQcdlNrmpJNOStRdu3bN+9znnntuop41a1aidjuw6stjyLp165ZaN3fu3II/z5IlS+qcjX979957b1SRDfoHH3wQ/eMf/0hMpPDSSy9Fu+yyS9S/f/9owoQJ0f/8z/9En//857MBv/TSS7P3GDz++OML8fSQII+ERB4JiTwSEnkkJPJIRTXoCxcujEaMGLGtPv/887NfTz311Gj69OnRBRdckJ0Vd9y4cdHatWujL37xi9Ef//jHqE2bNoV4ekiQR0Iij4REHgmJPBISeaSiGvThw4fX+7Gt+FZNV1xxRXaBYpNHQiKPhEQeCYk8EhJ5pGquQadu8Ttvuf70pz8l6qOPProZRwTQfOKPDW5vwYIFea9B/9KXvpSoDz/88EQ9ZcqU1D7xxxLrc/nll6fW1XWPYcgnd76D+Jf5XLm//MfXs27vyiuvLNLoaA59+/ZN1HfccUfea9Drkjsn0W233Zaor7nmmrzX2uaOBSrR3//+93rns1mzZk1qn08++SQKXZC3WQMAAIBqo0EHAACAAGjQAQAAIAAadAAAAAiASeIAaHabNm1K1OvXr8+7T3y/2e09+OCDDZ6UK3fSpoceemiHxgu5pk2blqhz74Vc12zQr7zySqI+5ZRTEvXGjRsLOkaa1+uvv56o99tvv9Q28T2088n9ebhs2bKoObRv375ZnofKsueee6bWtW3btsmPu2XLlkQd394u18yZM/NOwl2OnEEHAACAAGjQAQAAIAAadAAAAAiAa9DLSLdu3Uo9BKpM7jW9dV1TCYWwdOnSojzurFmzEvW1116bqJcvX16U56WyHHTQQal1udec9+7dO+/j3HrrrYn67bffLsDoKJe5NmKLFi1qlud+//33E3VtbW1qm9zMHnvssYl6+vTpRRod5WTnnZPt4ogRIxL1bbfdltqna9euDcpn7Mknn0zUkyZNqncOj0rmDDoAAAAEQIMOAAAAAdCgAwAAQABcg15Gcq8NgmJzzTnFstNOOyXqQw89NO89zfN5/PHHU+uOOeaYRowOkr73ve+l1u2666717vPaa6+l1j388MMFHRf8N2vWrEnUb731Vt5r0OfNm1f0cRG2gQMHptbl3n/8wgsvbPLzTJw4MbXupptuavLjVgpn0AEAACAAGnQAAAAIgAYdAAAAAqBBBwAAgACYJC4gc+fOTdRHH310ycYCO+KVV14p9RAoUzNmzEjUJ5xwQpMnKDSpIYUyYcKERD127NgG5+3www9PrVuxYkUBRgfFsXLlylIPgWY2ePDgRP2HP/whtU3fvn0L/rxvvvlmwR+zkjiDDgAAAAHQoAMAAEAANOgAAAAQANegB2TZsmX1/nnLli1T6wYMGJColy5dWvBxwX/zz3/+s9RDIEB9+vRJ1GPGjEltM3r06Hqv533xxRdT+7z88sv1Pm7Pnj0bNV7o169fvdect2iRPp+xefPmRH3bbbclatebE7rcn7urV68u2Vgovv333z+17qGHHqr39bsuuT/7HnvssdQ2xx13XKPGyP9xBh0AAAACoEEHAACAAGjQAQAAIACuQQ/Ip59+Wu+f19TUpNa1bt26iCMCaLgvf/nLifqKK67Iu88ll1ySqH/1q1+ltjn++OPrvQZ98eLFDRwp1Wj33XdPrXvkkUcS9Z577pn3caZMmZKoL7zwwgKMDtIZ3WWXXfLus2HDhkT97rvv1pvX2M9//vNE3aNHj3rrWLt27RL1lVdemagfeOCBvP++KI3f/e53qXU7cs35rFmzEvW1116b99p216A3jTPoAAAAEAANOgAAAARAgw4AAAAB0KADAABAAEwSF5CHH344Ub/++uuJeq+99krtM2HChER91llnFWl0kGaSQoYPH55ad8MNN+Td79hjj03UTz31VKLu3bt3ap/LLrus3sdcsmRJ3ueFuiaA25FJ4XKZ+Ip8WrVqlVr32c9+NlGffvrpqW3GjRuXqNu3b5/3uT7++ONE/cEHHzR4orn77rsvUb/99tt5/06dO3dO1LW1tal9/FspjW9/+9uJeo899khts3DhwkQ9cuTI1DYbN25M1J988kmi/sEPftDEkZLLGXQAAACohAZ98uTJ0YEHHhh17Ngx6tmzZ/Y2OG+88UbqnZfx48dH3bp1izp06BCNHj06WrVqVVOfGlLkkZDII6GRSUIij4REHqmYBv2ZZ57JBnXBggXR7Nmzsx97OOKII6IPP/xw2zbnnXde9Oijj0b3339/dvsVK1ZEJ5xwQlOfGlLkkZDII6GRSUIij4REHglFTSaTyRTyAePrVeJ3neLQHnbYYdG6deuiHj16RPfcc0904oknbru2eu+9947mz58fDR06NO9jrl+/PnWNSzW4/vrrE/WYMWNS2/Tq1ave60TKUZyZTp06BZvHSs1kv379UuuWLl1a7z7nnntuat3UqVOjSiKP9Zs0aVJq3cSJExN1/PfNNWLEiETdsmXLvNe05V6D3r1790Qd/2KV66abbooqTeiZDP3n43e+853UuunTp9e7z7x581Lrth6frd57772oGsnjf/+dLPf3uNg3vvGNJj/PypUrU+u2bNmSqBcvXpyoX3755ag53H333al1ixYtippLNecxdy6NJ554IlEPGDAgtc9zzz2XqA8++OAGP29dvUfua/rq1asT9ZFHHpna55VXXokqzbpG5rFFMQay/WQUL7zwQvYdqO0nHYgnO+vfv382zHXZtGlTNsDbL1CqPMZkkkKQR0LjNZuQyCMhkUdKpaANevzuXTyr+CGHHBINGjRo22yO8YyPXbp0Sb3LWNdMj1uvAYnfXdq61HVmD5orjzGZpKnkkdB4zSYk8khI5JGKadDjjxfGH2OZMWNGkx4n/nhk/K7V1mX58uUFGyPVo1B5jMkkTSWPhMZrNiGRR0Iij1TEfdDPPvvs6LHHHoueffbZqG/fvol72cb3Zly7dm3iHad4xsO67nO79d7K7q+cVtd0Abn3vaTweayWTNY1C+mrr76aqPfdd99mHFHlqOQ85l73WNfPqrp+duVenxbPlru9X/7yl6l9cq/xvf322yv+evNiqebX7CuvvLLB+9SVrWq95rwYKiWP3/rWtwpyvfnjjz+eqH/xi18k6r/85S+pfXLvTU315TH32vb4evl8Zs2alXeb3Punn3rqqYl6553zt5MzZ86s+OvNgzqDHv/iFQc5PvBPP/10tNtuuyX+fPDgwdlfxObMmbNtXXzLgmXLlkXDhg1r6tNDgjwSEnkkNDJJSOSRkMgjFXMGPf4ISDyb4cMPP5y9b+DWazDid3Hatm2b/Tp27Njo/PPPz06yEM9kd84552SDvKMzFMOOkkdCIo+ERiYJiTwSEnmkYhr0rR/7Gj58eGL9nXfeGZ122mnZ/58yZUrUokWLaPTo0dnZDOOp9W+88camPjWkyCMhkUdCI5OERB4JiTxSMQ36jtxGvU2bNtG0adOyCxSTPBISeSQ0MklI5JGQyCMVN0kcxVfXje6PO+64eidhgB1V14SDGzdurHefww8/PLVu6tSpBR0XYevZs2febd5+++3UutmzZyfqQw89NO/jjBkzJlE/+uijOzRGqlvu5Jbt27fPu8/ll1+eqB988MGCj4vKk/s72NazrttbsWJFor7vvvtS28RnbKGhcs/8t2vXLu8+5513XqIeMmRIaputt5nbKr7vez7PPPNMor7ooovy7kORbrMGAAAANI4GHQAAAAKgQQcAAIAAuAY9YCeddFKijmeLzPXaa68144ioNi+99FLqHqDb69ChQzOPiNDsyM+gE088MbWupqYmUb/77ruJuq4JeJ566qlGjZHqlnv7o/j2Sfnkvt7uyORRsGTJkkS93377lWwsVJ9Zs2Yl6gkTJiTqXr16pfbp0qVLoh41alSDn3fDhg2pddddd12iXr9+fYMft5o5gw4AAAAB0KADAABAADToAAAAEAANOgAAAATAJHEBe/bZZxP13nvvndrmo48+asYRUW0mTZqUqAcNGpSo77vvvmYeEaG56667UutatWqVqC+99NLUNgsXLkzUjzzySKKeMmVKwcZIdbvjjjsS9WWXXZbapl27don6ySefLPq4AApp0aJFifqII45I1LNnz07t07NnzwY/T+7Px2uuuSa1zdy5cxv8uPyHM+gAAAAQAA06AAAABECDDgAAAAGoyWQymShw8c3tO3fuXOph0EzWrVsXderUKQqZTFYPeSQ0oWdSHquLPBISeaQS8ugMOgAAAARAgw4AAAAB0KADAABAADToAAAAEAANOgAAAARAgw4AAAAB0KADAABAADToAAAAEAANOgAAAARAgw4AAAAB0KADAABAAMqiQc9kMqUeAs2oHL7f5TBGqud7XQ5jpHq+36GPj+r6foc+Pqrr+x36+Ajj+10WDfr7779f6iHQjMrh+10OY6R6vtflMEaq5/sd+vioru936OOjur7foY+PML7fNZkyeCtny5Yt0YoVK6KOHTtm/6L9+vWLli9fHnXq1KnUQ6so69evL+mxjaMYf3/79OkTtWjRoiwyGY+5f//+8lgE8rjj5LHy81hOmZTH4pPHHSePxSePO04ei299BeRx56gMxH+xvn37Zv+/pqYm+zU+4AJdHKU8tp07d47KKZPxD4GYPBaPPOYnj82n1Me2HDIpj82n1MdWHtleqY+tPLK9cs5juG8xAQAAQBXRoAMAAEAAyq5Bb926dfSTn/wk+5XCcmwbzjErHse24Ryz4nFsG84xKx7HtuEcs+JxbBvOMSue1hVwbMtikjgAAACodGV3Bh0AAAAqkQYdAAAAAqBBBwAAgABo0AEAACAAZdWgT5s2LRo4cGDUpk2baMiQIdHzzz9f6iGVncmTJ0cHHnhg1LFjx6hnz57R8ccfH73xxhuJbTZu3BiNHz8+6tatW9ShQ4do9OjR0apVq0o25lDJY9PJY+HIY9PJY2HJZNPIY2HJY9PIY2HJY9NNruRMZsrEjBkzMq1atcr8+te/zrz66quZ008/PdOlS5fMqlWrSj20snLkkUdm7rzzzsyiRYsyL730UmbUqFGZ/v37Zz744INt25xxxhmZfv36ZebMmZNZuHBhZujQoZmDDz64pOMOjTwWhjwWhjwWhjwWjkw2nTwWjjw2nTwWjjwWxpEVnMmyadAPOuigzPjx47fVmzdvzvTp0yczefLkko6r3K1evTq+zV7mmWeeydZr167NtGzZMnP//fdv2+a1117LbjN//vwSjjQs8lgc8tg48lgc8th4Mll48th48lh48th48lgcqysok2XxEfePP/44euGFF6KRI0duW9eiRYtsPX/+/JKOrdytW7cu+3WXXXbJfo2P8yeffJI41nvttVfUv39/x/r/yWPxyGPDyWPxyGPjyGRxyGPjyGNxyGPjyGPxrKugTJZFg/7OO+9Emzdvjnr16pVYH9e1tbUlG1e527JlSzRhwoTokEMOiQYNGpRdFx/PVq1aRV26dEls61j/hzwWhzw2jjwWhzw2nkwWnjw2njwWnjw2njwWx5YKy+TOpR4ApRNPmrBo0aLoz3/+c6mHAvJIUOSRkMgjIZFHQjO+wjJZFmfQu3fvHu20006pWffiunfv3iUbVzk7++yzo8ceeyyaO3du1Ldv323r4+MZf/xm7dq1ie0d6/+Qx8KTx8aTx8KTx6aRycKSx6aRx8KSx6aRx8I7uwIzWRYNevzxhMGDB0dz5sxJfJQhrocNG1bSsZWbeGLAOMgzZ86Mnn766Wi33XZL/Hl8nFu2bJk41vEtC5YtW+ZY/z95LBx5bDp5LBx5LAyZLAx5LAx5LAx5LAx5LJxMJWcyU0a3JGjdunVm+vTpmcWLF2fGjRuXvSVBbW1tqYdWVs4888xM586dM/PmzcusXLly27Jhw4bELQni2xQ8/fTT2VsSDBs2LLvwH/JYGPJYGPJYGPJYODLZdPJYOPLYdPJYOPJYGGdWcCbLpkGPTZ06NXuQ43sHxrcoWLBgQamHVHbi92TqWuL7CG710UcfZc4666xM165dM+3atct87WtfywaeJHlsOnksHHlsOnksLJlsGnksLHlsGnksLHlsuqiCM1kT/6fUZ/EBAACg2pXFNegAAABQ6TToAAAAEAANOgAAAARAgw4AAAAB0KADAABAADToAAAAEAANOgAAAARAgw4AAAAB0KADAABAADToAAAAEAANOgAAAARAgw4AAAAB0KADAABAADToAAAAEAANOgAAAARAgw4AAAAB0KADAABAADToAAAAEAANOgAAAARAgw4AAAAB0KADAABAADToAAAAEAANOgAAAARAgw4AAAAB0KADAABAADToAAAAEAANOgAAAARAgw4AAAAB0KADAABAADToAAAAEAANOgAAAARAgw4AAAAB0KADAABAADToAAAAEAANOgAAAARAgw4AAAAB0KADAABAADToAAAAEAANOgAAAARAgw4AAAAB0KADAABAADToAAAAEAANOgAAAARAgw4AAAAB0KADAABAADToAAAAEAANOgAAAARAgw4AAAAB0KADAABAADToAAAAEAANOgAAAARAgw4AAAAB0KADAABAADToAAAAEAANOgAAAARAgw4AAAAB0KADAABAADToAAAAEAANOgAAAARAgw4AAAAB0KADAABAADToAAAAEAANOgAAAARAgw4AAAAB0KADAABAADToAAAAEAANOgAAAESl97/3EmBL0ub7pQAAAABJRU5ErkJggg==", "text/html": [ "\n", "