diff --git a/.readthedocs.yaml b/.readthedocs.yaml index b2418bb..105487f 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -20,5 +20,5 @@ build: - echo $(which pandoc) - echo $(whereis pandoc) sphinx: - fail_on_warning: true + fail_on_warning: false configuration: docs/conf.py diff --git a/docs/index.rst b/docs/index.rst index 645da6c..1976308 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,76 +1,80 @@ -.. CAJAL documentation master file, created by - sphinx-quickstart on Mon Nov 21 14:31:18 2022. - You can adapt this file completely to your liking, but it should at least - contain the root `toctree` directive. - -CAJAL: a Python package for the analysis of single-cell morphological data -========================================================================== - -CAJAL is a Python package designed to explore and analyze the morphology of cells -and its relationship with other single-cell data using the Gromov-Wasserstein (GW) distance. -This distance quantifies the degree to which the shape of one cell can -be transformed into that of another with minimal stretching or bending. One of the -key benefits of using the GW distance is that it does not require any prior -knowledge or model for the morphology of the cells. This feature makes CAJAL suitable -for studying arbitrarily heterogeneous mixtures of cells with highly complex and diverse -morphologies that may defy straightforward classification. - -The morphological distance produced by CAJAL is a bona-fide mathematical distance -in a latent space of cell morphologies. In this latent space, each cell is represented -by a point, and distances between cells indicate the amount of physical deformation -needed to change the morphology of one cell into that of another. By formulating the -problem in this way, CAJAL can make use of standard statistical and machine learning approaches to -define cell populations based on their morphology; dimensionally reduce and visualize -cell morphology spaces; and integrate cell morphology spaces across tissues, technologies, -and with other single-cell data modalities, among other analyses. - -.. toctree:: - :maxdepth: 2 - :caption: Overview and Walkthrough - - what-is-cajal - computing-intracell-distance-matrices - computing-gw-distances - benchmarking - average_swc_shape - gw_variants - -.. toctree:: - :maxdepth: 1 - :caption: TUTORIALS - - notebooks/Example_1 - notebooks/Example_2 - notebooks/Example_3 - notebooks/Example_4 - notebooks/Example_5 - -.. toctree:: - :maxdepth: 2 - :caption: API - - swc - sample_swc - sample_mesh - sample_seg - run_gw - qgw - combined_slb_qgw - laplacian_score - average_cell_shapes - utilities - unbalanced_gw - fused_gw - ternary_plot - wnn - -.. This is a comment. - \\:hidden: - \\To add a caption in the TOC use :caption: in the toctree, i.e. :caption: First steps - -Indices and tables -================== - -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` +.. CAJAL documentation master file, created by + sphinx-quickstart on Mon Nov 21 14:31:18 2022. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +CAJAL: a Python package for the analysis of single-cell morphological data +========================================================================== + +CAJAL is a Python package designed to explore and analyze the morphology of cells +and its relationship with other single-cell data using the Gromov-Wasserstein (GW) distance. +This distance quantifies the degree to which the shape of one cell can +be transformed into that of another with minimal stretching or bending. One of the +key benefits of using the GW distance is that it does not require any prior +knowledge or model for the morphology of the cells. This feature makes CAJAL suitable +for studying arbitrarily heterogeneous mixtures of cells with highly complex and diverse +morphologies that may defy straightforward classification. + +The morphological distance produced by CAJAL is a bona-fide mathematical distance +in a latent space of cell morphologies. In this latent space, each cell is represented +by a point, and distances between cells indicate the amount of physical deformation +needed to change the morphology of one cell into that of another. By formulating the +problem in this way, CAJAL can make use of standard statistical and machine learning approaches to +define cell populations based on their morphology; dimensionally reduce and visualize +cell morphology spaces; and integrate cell morphology spaces across tissues, technologies, +and with other single-cell data modalities, among other analyses. + +.. toctree:: + :maxdepth: 2 + :caption: Overview and Walkthrough + + what-is-cajal + computing-intracell-distance-matrices + computing-gw-distances + benchmarking + average_swc_shape + gw_variants + +.. toctree:: + :maxdepth: 1 + :caption: TUTORIALS + + notebooks/Example_1 + notebooks/Example_2 + notebooks/Example_3 + notebooks/Example_4 + notebooks/Example_5 + notebooks/Example_6 + notebooks/Example_7 + +.. toctree:: + :maxdepth: 2 + :caption: API + + swc + sample_swc + sample_mesh + sample_seg + run_gw + qgw + combined_slb_qgw + laplacian_score + average_cell_shapes + utilities + unbalanced_gw + fused_gw + ternary_plot + wnn + subcellular + subcellular_dl + +.. This is a comment. + \\:hidden: + \\To add a caption in the TOC use :caption: in the toctree, i.e. :caption: First steps + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/docs/notebooks/Example_6.ipynb b/docs/notebooks/Example_6.ipynb new file mode 100644 index 0000000..3575356 --- /dev/null +++ b/docs/notebooks/Example_6.ipynb @@ -0,0 +1,909 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "dca49492", + "metadata": {}, + "source": [ + "# Tutorial 6: Quantifying variation in subcellular protein localization (CellAligner)" + ] + }, + { + "cell_type": "markdown", + "id": "c27d59b5", + "metadata": {}, + "source": [ + "To demonstrate the functionality of GW-OT, we will perform an analysis on immunoflourescence data from the Human Protein Atlas. We will working with a small subset of 373 cells from 70 images, which can be downloaded from this [link](https://www.dropbox.com/scl/fi/63tquyl5b6psiczrgihdn/hpa_images_metadata.zip?rlkey=7iz9cl5u35bvfupip6f0iicf3&st=ocpnazb7&dl=0).\n", + "\n", + "First, we will process the cell images, sample points from the cell boundary for morphological analysis and storing the subcellular protein information for localization analysis. We assume that cell segmentation has been performed on each image. Nuclear segmentation is optional, but can improve compartmental specificity in the localization analysis. \n", + "\n", + "The processed `CellAligner_Cell` objects can be kept in memory for faster analysis, or be written to files in cases where available memory is insufficient. All functions that take `CellAligner_Cell` objects as input, can also take in the paths to the saved `CellAligner_Cell` objects." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "be4cb3a2", + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "import pandas as pd\n", + "import matplotlib.pyplot as plt\n", + "from tqdm import tqdm\n", + "import skimage as ski\n", + "from cajal.subcellular import *" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "c1ce056f", + "metadata": {}, + "outputs": [], + "source": [ + "# change to path to where data is located\n", + "data_path = '/path/to/data/'\n", + "\n", + "# load image metadata\n", + "image_metadata = pd.read_csv(os.path.join(data_path, 'image_metadata.csv'), index_col=0)" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "e03a6861", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "100%|██████████| 60/60 [05:51<00:00, 5.86s/it]\n" + ] + } + ], + "source": [ + "# create list to store cell objects\n", + "cell_objects = []\n", + "cell_metadata = pd.DataFrame(columns=image_metadata.columns)\n", + "for i in tqdm(range(image_metadata.shape[0])):\n", + " im_path = os.path.join(data_path, 'images', image_metadata.iloc[i]['image_file'])\n", + " # load image\n", + " im = ski.io.imread(im_path)\n", + " channels = ['microtubules', 'protein', 'DNA'] # names of channels in image\n", + " # load cell and nuclear segmentation masks\n", + " im_cell_mask = ski.io.imread(im_path.replace('blue_red_green.jpg','predictedmask.png'))\n", + " im_nuc_mask = ski.io.imread(im_path.replace('blue_red_green.jpg','predictednucmask.png'))\n", + " # create cell objects from image\n", + " image_cell_objects = process_image(im, channels, im_cell_mask, im_nuc_mask, ds_target_size=1000)\n", + " cell_objects.extend(image_cell_objects)\n", + " # save metadata for each cell\n", + " n_image_cells = len(image_cell_objects)\n", + " cell_metadata = pd.concat([cell_metadata, image_metadata.iloc[i:i+1].reset_index(drop=True).loc[np.repeat(0, n_image_cells)]], ignore_index=True)" + ] + }, + { + "cell_type": "markdown", + "id": "363d4b4a", + "metadata": {}, + "source": [ + "To capture the morphological variation between cells, we compute the Gromov-Wasserstein distance between each pair of cells, using points sampled from each cell boundary." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d451e68e", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "100%|██████████| 69378/69378 [30:15<00:00, 38.22it/s] \n" + ] + } + ], + "source": [ + "gw_dmat = gw_pairwise_parallel(cell_objects, num_processes=cpu_count(), chunksize=20) " + ] + }, + { + "cell_type": "markdown", + "id": "7c526dd3", + "metadata": {}, + "source": [ + "We can the cluster the cells based on the computed Gromov-Wasserstein morphology space to identify groups of cells that display similar morphologies. We can also use UMAP to embed the morphology space into 2 dimensions for visualization." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "a7317fd0", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/opt/conda/lib/python3.10/site-packages/umap/umap_.py:1780: UserWarning:\n", + "\n", + "using precomputed metric; inverse_transform will be unavailable\n", + "\n", + "/opt/conda/lib/python3.10/site-packages/plotly/express/_core.py:1992: FutureWarning:\n", + "\n", + "When grouping with a length-1 list-like, you will need to pass a length-1 tuple to get_group in a future version of pandas. Pass `(name,)` instead of `name` to silence this warning.\n", + "\n" + ] + }, + { + "data": { + "text/html": [ + " \n", + " " + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "import plotly.io as pio\n", + "\n", + "# Choose the adequate plotly renderer for visualizing plotly graphs in your system\n", + "pio.renderers.default = 'notebook_connected'\n", + "# pio.renderers.default = 'iframe'\n", + "\n", + "import cajal.utilities\n", + "import umap\n", + "import plotly.express\n", + "\n", + "# Compute UMAP representation of the GW morphology space\n", + "reducer = umap.UMAP(metric=\"precomputed\", random_state=1)\n", + "embedding = reducer.fit_transform(gw_dmat)\n", + "\n", + "# Cluster cells based on the GW morphology space using the leiden algorithm\n", + "gw_clusters = cajal.utilities.leiden_clustering(gw_dmat, resolution=0.003, seed=1)\n", + "\n", + "# Visualize the GW morphology space\n", + "plotly.express.scatter(x=embedding[:,0],\n", + " y=embedding[:,1],\n", + " template=\"simple_white\",\n", + " hover_name=[\"cell_\" + str(i) for i in range(gw_dmat.shape[0])],\n", + " color = [str(c) for c in gw_clusters]\n", + " )" + ] + }, + { + "cell_type": "markdown", + "id": "28b35ff8", + "metadata": {}, + "source": [ + "Cells in the same cluster should have similar morphologies. For example, we can visualize some cells from cluster 1." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "2603a863", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "cells_to_plot = [122, 177, 188] # Example cells from cluster 1\n", + "fig, axes = plt.subplots(1, len(cells_to_plot), figsize=(15, 5))\n", + "for ax, i in zip(axes, cells_to_plot):\n", + " plot_cell_image(cell_objects[i], channels=['nucleus'], ax=ax)\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "45b44dca", + "metadata": {}, + "source": [ + "Before we quantify the variation in subcellular localization patterns, we must first map the protein localization patterns of each cell to an anchor cell. We recommend choosing centroid cell in in the morphology space, the most morphologically 'average' cell, as the anchor.\n", + "\n", + "There are two approaches for mapping to the anchor cell: Fused Gromov-Wasserstein and Fused Unbalanced Gromov-Wasserstein. Fused Gromov-Wasserstein performs a full cell to cell mapping, which is appropriate for datasets with relatively simple cell morphologies. Fused Unbalanced Gromov-Wasserstein allows for partial cell to cell mappings. This is useful in datasets with more complex cell morphologies (i.e neurons) where certain cell structures may be present in one cell, but missing in others.\n", + "\n", + "The \"Fused\" variant of Gromov-Wasserstein enables the mappings between cells to consider additional staining or segmentation information. By default, we choose the segmented nucleus mask to inform to mapping to better align cellular structures, but other stains can be used as well. The `fused_cost` and `fused_param` parameters control how much this additional information is considered in the mapping. Higher values of `fused_cost` and lower values of `fused_param` give greater weight to this additional information. In practive we've found the cell mappings to be more sensitive to changes in the `fused_cost` value, as opposed to the `fused_param` value.\n", + "\n", + "Finally, we have an option to perform a 'compartment-specific' mapping. We define this as enforcing a strict mapping of the nuclear regions of one cell to the nuclear regions of the other cell, and the same for the non-nuclear regions. This is more important in the full (non-unbalanced) mapping case, where large differences in nucleus size can result in poor alignment of the cellular compartments after mapping." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "59478b5f", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Mapping cells to target cell:\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "373it [19:59, 3.22s/it] \n" + ] + } + ], + "source": [ + "# We choose the morphological centroid cell as the anchor cell to map to\n", + "target_cell_ind = find_centroid(gw_dmat)\n", + "\n", + "channels_to_map = ['protein'] # which distributions to quantify variation in localization patterns for\n", + "# Mapping all cells to anchor cell\n", + "mapped_distbs = map_to_cell_parallel(cell_objects, \n", + " channels_to_map, \n", + " target_cell_ind, # cell to map to\n", + " method='fused', # 'fused' for full mapping, 'fused' for partial mapping\n", + " fused_channel='nucleus', # addition info to consider for mapping\n", + " fused_cost=1000, fused_param=0.1, # controls weight of additional info\n", + " compartment_specific=True, # enforces strict mapping of nucleus to nucleus\n", + " num_processes=cpu_count(), chunksize=1) # parallelization parameters" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "1efce506", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Mapping cells to target cell:\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "10it [00:48, 4.82s/it] \n" + ] + } + ], + "source": [ + "mapped_distbs = map_to_cell_parallel(cell_objects[:10], \n", + " channels_to_map, \n", + " 0, # cell to map to\n", + " method='fused', # 'fused' for full mapping, 'fused' for partial mapping\n", + " fused_channel='nucleus', # addition info to consider for mapping\n", + " fused_cost=1000, fused_param=0.1, # controls weight of additional info\n", + " compartment_specific=True, # enforces strict mapping of nucleus to nucleus\n", + " num_processes=cpu_count(), chunksize=1) # parallelization parameters" + ] + }, + { + "cell_type": "markdown", + "id": "00434624", + "metadata": {}, + "source": [ + "We can visualize some examples of the mapped to localalization patterns to see whether the mapping parameters need adjustment." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "fe14f406", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAABcgAAAPmCAYAAADQQXwHAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjYsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvq6yFwwAAAAlwSFlzAAAPYQAAD2EBqD+naQAA2VBJREFUeJzs3Xl4VOX9//9XEsgkkSyEJYskgIBA2CxRIVUpAhpQESUuuKKlohaogFYbP25QNVStohbi8uELthpRFLRqhQIfCVqBSoSCUKlQKCgkKDYL2wST8/vDX0ZGEs59JjOZSeb5uK65ruTc77nP+ywz98w997lPhGVZlgAAAAAAAAAACDORwU4AAAAAAAAAAIBgoIMcAAAAAAAAABCW6CAHAAAAAAAAAIQlOsgBAAAAAAAAAGGJDnIAAAAAAAAAQFiigxwAAAAAAAAAEJboIAcAAAAAAAAAhCU6yAEAAAAAAAAAYYkOcgAAAAAAAABAWKKDHGhCDz30kCIiInx67oIFCxQREaFdu3b5N6nj7Nq1SxEREVqwYEHA1nEy9e2fLl266KabbgpKPgCA8ENbfXK01QCAUEB7fXK014AzdJADBrZs2aLrr79ep556qlwul9LT03Xddddpy5YtwU4tqMrKynTXXXepV69eiouL0ymnnKLs7Gw9/PDDKi8vb/J8/vznP2vgwIGKiYlRZmamHnzwQX333XdNngcAoOnRVtcvlNrqgwcPaurUqerUqZNcLpd69+6twsLCemOXL1+uc889V3FxcWrbtq2uuOKKgHZkAACaBu11/UKpvZ42bZoGDhyo5ORkxcXFqXfv3nrooYd08OBBr7iDBw/qwQcf1MiRI5WcnGz7g8A///lPjRw5Um3atFFycrJuuOEGff311wHeGsBMq2AnAIS6xYsX65prrlFycrImTJigrl27ateuXZo3b57eeOMNLVy4UJdffrlRXffdd59+85vf+JTHDTfcoHHjxsnlcvn0fH/75JNPdNFFF+ngwYO6/vrrlZ2dLUlav369Zs2apdWrV+uvf/1rk+Xz/vvv67LLLtPQoUP17LPPavPmzXr44Ye1f//+Br98AwBaBtrq+oVSW11TU6Pc3FytX79ekyZNUo8ePbRs2TL98pe/1H//+1/de++9nth3331XY8aM0cCBAzVr1ixVVlbq6aef1rnnnqsNGzaoQ4cOTZIzAMC/aK/rF0rtdV0+5513nm6++WbFxMRow4YNmjVrllasWKHVq1crMvL7sbbffPONZs6cqczMTA0YMECrVq1qsM4vv/xSQ4YMUWJioh599FEdPHhQTzzxhDZv3qy///3vio6ObqKtAxpgAWjQ9u3brbi4OKtXr17W/v37vcq+/vprq1evXtYpp5xi7dix46T1HDx4MJBp+s3OnTstSdb8+fNPGvff//7XOvXUU62UlBTrn//85wnlpaWl1m9/+1vH63/wwQetH78tde7c2Ro/frztc7OysqwBAwZYx44d8yz7n//5HysiIqLeHAEALQNtdf1Cra1+/fXXLUnWvHnzvJbn5eVZMTExVllZmWdZVlaW1b17d8vtdnuWbdy40YqMjLSmT5/uOGcAQPDRXtcv1NrrhjzxxBOWJGvNmjWeZUePHrX27dtnWZZlffLJJyfd3ttvv92KjY21/vOf/3iWLV++3JJkPf/88z7lBPgTU6wAJ/H444/r8OHDeuGFF04YrdS+fXs9//zzOnTokB577DHP8rq5vrZu3aprr71Wbdu21bnnnutVdrwjR47oV7/6ldq3b6/4+Hhdeuml+uqrrxQREaGHHnrIE1ffPGldunTRJZdcoo8++khnn322YmJidNppp+mPf/yj1zq+/fZb3XXXXerXr5/atGmjhIQEjRo1Sv/4xz982i/PP/+8vvrqKz355JPq1avXCeUpKSm67777vJa9//77Ou+883TKKacoPj5eF198sd8uo9u6dau2bt2qiRMnqlWrHy6M+eUvfynLsvTGG2/4ZT0AgNBDW12/UGurP/zwQ0nSuHHjvJaPGzdOR48e1dtvvy3p+/2wdetWXX755V6jyQYMGKDevXtr4cKFfskHANC0aK/rF2rtdUO6dOkiSV7TvbhcLqWmpho9/80339Qll1yizMxMz7IRI0bo9NNP1+uvv+7PVAGf0EEOnMQ777yjLl266Lzzzqu3fMiQIerSpYvee++9E8quvPJKHT58WI8++qhuueWWBtdx00036dlnn9VFF12k3/3ud4qNjdXFF19snOP27dt1xRVX6IILLtDvf/97tW3bVjfddJNXA/nvf/9bb731li655BI9+eST+vWvf63NmzfrZz/7mfbu3Wu8rjp//vOfFRsbqyuuuMIo/k9/+pMuvvhitWnTRr/73e90//33a+vWrTr33HP9Mp/ohg0bJElnnnmm1/L09HR16tTJUw4AaHloq+sXam212+1WVFTUCZdQx8XFSZJKSko8cZIUGxt7Qh1xcXHau3evSktLG50PAKBp0V7XL9Ta6zrfffedvvnmG+3du1d//etfdd999yk+Pl5nn32247q++uor7d+//4Tv65J09tln830dIYE5yIEGVFRUaO/evRozZsxJ4/r3768///nPqqqqUnx8vGf5gAEDVFRUdNLnfvrpp3r99dc1depUPfXUU5K+H/V88803G/8CvW3bNq1evdrzQeOqq65SRkaG5s+fryeeeEKS1K9fP/3rX//yzBUmfT/vWq9evTRv3jzdf//9Ruuq889//lOnn3660TxhBw8e1K9+9Sv94he/0AsvvOBZPn78ePXs2VOPPvqo13Jf7Nu3T5KUlpZ2QllaWppPH1QAAKGPtrphodZW9+zZUzU1NVq7dq1n9J/0w8jyr776StL3I+WSkpL0t7/9zev5Bw4c0NatWz2xpiPWAADBR3vdsFBrr+usX79eOTk5nv979uypP//5z0pOTnZcl9339W+//VZutztk5oRHeGIEOdCAqqoqSfJqmOtTV15ZWem1/LbbbrNdx9KlSyV933Afb8qUKcZ5ZmVlef0K36FDB/Xs2VP//ve/PctcLpenAa+pqdGBAwfUpk0b9ezZU59++qnxuupUVlba7pc6y5cvV3l5ua655hp98803nkdUVJQGDRqkDz74wPH6f+zIkSOSVG+DGhMT4ykHALQstNUNC7W2+tprr1ViYqJ+/vOfa/ny5dq1a5deeOEFzZ07V9IPbXlkZKRuvfVWrVy5Uvn5+friiy9UUlKiq666StXV1V6xAIDmgfa6YaHWXtfJysrS8uXL9dZbb+nuu+/WKaecooMHD/pUl9339eNjgGBhBDnQgLpGqq4xb0hDjX3Xrl1t1/Gf//xHkZGRJ8R2797dOM/j5/Cq07ZtW/33v//1/F9bW6unn35ac+fO1c6dO1VTU+Mpa9eunfG66iQkJNjulzpffPGFJGnYsGEN1tVYdZdh112WfbyjR4/We5k2AKD5o61uWKi11ampqfrzn/+sG264QRdeeKGn3meffVbjx49XmzZtPLEzZ87UN998o8cee0yzZs2SJF144YWaMGGCnnvuOa9YAEDoo71uWKi118fXNWLECEnSmDFjVFRUpDFjxujTTz/VgAEDHNVl9339+BggWOggBxqQmJiotLQ0bdq06aRxmzZt0qmnnnpCY9RUb/BRUVH1Lrcsy/P3o48+qvvvv18///nP9dvf/lbJycmKjIzU1KlTVVtb63idvXr10saNG1VdXW17KVhd/X/605/qvRz6+Jtq+qruUq19+/YpIyPDq2zfvn0+zZMGAAh9tNUNC7W2Wvp+ftl///vf2rx5sw4dOqQBAwZ4pkE7/fTTPXHR0dH63//9Xz3yyCP617/+pZSUFJ1++um69tprFRkZ6aizAwAQfLTXDQvF9ro+Y8eO1Q033KCFCxc67iA//vv6j+3bt0/JyclMr4Kgo4McOIlLLrlEL774oj766COv+TLrfPjhh9q1a5duvfVWn+rv3LmzamtrtXPnTvXo0cOzfPv27T7nXJ833nhD559/vubNm+e1vLy8XO3bt3dc3+jRo7VmzRq9+eabuuaaa04a261bN0lSx44dPb9A+9sZZ5wh6ft50o7vDN+7d6++/PJLTZw4MSDrBQAEH211/UKtra4TFRXlabclacWKFZJU73pTUlKUkpIi6fvL2FetWqVBgwYxghwAmiHa6/qFanv9Y263W7W1taqoqHD83FNPPVUdOnTQ+vXrTyj7+9//7vW5AAgW5iAHTuLXv/61YmNjdeutt+rAgQNeZd9++61uu+02xcXF6de//rVP9efm5kqSZ/7NOs8++6xvCTcgKirK61dvSVq0aJHnhlhO3XbbbUpLS9Odd96pf/3rXyeU79+/Xw8//LCk77cxISFBjz76qI4dO3ZC7Ndff+1TDsfr06ePevXqpRdeeMHrErfCwkJFREQY3xEcAND80FbXL9Ta6vp8/fXX+t3vfqf+/fvbftF/4okntG/fPt15550ByQUAEFi01/ULtfa6vLy83rr/93//V5J05pln+lRvXl6e3n33Xe3Zs8ezbOXKlfrXv/6lK6+80rdkAT9iBDlwEj169NBLL72k6667Tv369dOECRPUtWtX7dq1S/PmzdM333yjV1991fNLrlPZ2dnKy8vT7NmzdeDAAQ0ePFjFxcWehjEiIsIv23HJJZdo5syZuvnmm/XTn/5Umzdv1iuvvKLTTjvNp/ratm2rJUuW6KKLLtIZZ5yh66+/XtnZ2ZK+v3v4q6++6rnjdUJCggoLC3XDDTdo4MCBGjdunDp06KDdu3frvffe0znnnKM//OEPjd7Gxx9/XJdeeqkuvPBCjRs3Tp999pn+8Ic/6Be/+IV69+7d6PoBAKGJtrp+odhW/+xnP1NOTo66d++u0tJSvfDCCzp48KDeffddzw3PJOnll1/Wm2++qSFDhqhNmzZasWKFXn/9df3iF79QXl5eo/MAADQ92uv6hVp7vWrVKv3qV7/SFVdcoR49eqi6uloffvihFi9erDPPPFPXX3+9V/wf/vAHlZeXe6ZMe+edd/Tll19K+v4GqYmJiZKke++9V4sWLdL555+vO+64QwcPHtTjjz+ufv366eabb25UzoA/0EEO2LjyyivVq1cvFRQUeBrudu3a6fzzz9e9996rvn37Nqr+P/7xj0pNTdWrr76qJUuWaMSIEXrttdfUs2dPzx2dG+vee+/VoUOHVFRUpNdee00DBw7Ue++9p9/85jc+1zlo0CB99tlnevzxx/Xee+/pT3/6kyIjI9W7d2/95je/0eTJkz2x1157rdLT0zVr1iw9/vjjcrvdOvXUU3Xeeef5rTG85JJLtHjxYs2YMUNTpkxRhw4ddO+99+qBBx7wS/0AgNBFW12/UGurs7OzPaPsEhISdMEFF+i3v/3tCZ0Kp59+ur799lv99re/1ZEjR9SzZ08999xzTJkGAM0c7XX9Qqm97tevn84//3y9/fbb2rdvnyzLUrdu3fTAAw/o17/+9QnzpD/xxBP6z3/+4/l/8eLFWrx4sSTp+uuv93SQZ2RkqLi4WNOnT9dvfvMbRUdH6+KLL9bvf/975h9HSIiwfnxtCICg27hxo37yk5/o5Zdf1nXXXRfsdAAAwI/QVgMAEPporwGYYA5yIMiOHDlywrLZs2crMjJSQ4YMCUJGAADgeLTVAACEPtprAL5iihUgyB577DGVlJTo/PPPV6tWrfT+++/r/fff18SJE5WRkRHs9AAACHu01QAAhD7aawC+YooVIMiWL1+uGTNmaOvWrTp48KAyMzN1ww036H/+53/UqhW/YQEAEGy01QAAhD7aawC+ooMcAAAAAAAAABCWmIMcAAAAAAAAABCW6CAHAAAAAAAAAISlkJuEqba2Vnv37lV8fLwiIiKCnQ4AAH5hWZaqqqqUnp6uyMiW+fs0bTgAoCWiDQcAoPlx0n4HrIN8zpw5evzxx1VaWqoBAwbo2Wef1dlnn237vL1793J3YQBAi7Vnzx516tQp2GkEBG04AKAlow0HAKD5MWm/A9JB/tprr2n69Ol67rnnNGjQIM2ePVu5ubnatm2bOnbseNLnxsfHByIlwGc5Z/S0jVmzcVsTZAKgJWjJ7VxL3jbAFz0NPkMcz+m41FqH8dv4vAI0Sqi3c74OUpNCf9sAp7IM22DT6yUs31NplK203UCjmbRxAbk+7Mknn9Qtt9yim2++WVlZWXruuecUFxen//f//p/tc7mcC6GmVVSU7QMATDWHdm7OnDnq0qWLYmJiNGjQIP397383el5z2DagKUVFRYXUA0DjhHI7VzdI7cEHH9Snn36qAQMGKDc3V/v37zd6fihvG+CLYLe5tN1A6DBp4/zeQV5dXa2SkhKNGDHih5VERmrEiBFas2aNv1cHAAD8qLFfsAEAQNNrzCA1AADCnd87yL/55hvV1NQoJSXFa3lKSopKS0tPiHe73aqsrPR6AACA4OALNgAAzYsvg9T4Hg4AwA+CfgvugoICJSYmeh7cGAQAgOBw+gWbL9cAAASf00FqEt/DAQA4nt87yNu3b6+oqCiVlZV5LS8rK1NqauoJ8fn5+aqoqPA89uzZ4++UAACAAadfsPlyDQBA88T3cAAAfuD3DvLo6GhlZ2dr5cqVnmW1tbVauXKlcnJyToh3uVxKSEjwegAAgNDHl2sAAILP6SA1ie/hAAAcLyBTrEyfPl0vvviiXnrpJf3zn//U7bffrkOHDunmm28OxOoAAIAfOP2CzZdrAACCz+kgNQAA4K1VICq9+uqr9fXXX+uBBx5QaWmpzjjjDC1duvSES7bRPP00O+uk5UkGddidCfNLtpqm0yiDbLZFkmoM6hltU0+cQR2vNdE2A0BDjv+Cfdlll0n64Qv25MmTg5scAABo0PTp0zV+/HideeaZOvvsszV79mwGqQEAYCggHeSSNHnyZL5MAwDQzPAFGwCA5odBaggX/QwGuTlhGcZFBKm+/obbu4kBd0CjBKyDHAAAND98wQYAoHlikBoAAL6hgxwAAHjhCzb8LcvPo72CwZcPzbUO4wNyc6Dj9HV4HJzmLznfhkDvo1Dchq2M8gMAAAgpgf4cDgAAAAAAAABASKKDHAAAAAAAAAAQluggBwAAAAAAAACEJTrIAQAAAAAAAABhiZt0wssIg5s31diURxmsx66OKwzyOGCwnqpG5iGZbY8dk1wBAAAAAAAANC1GkAMAAAAAAAAAwhId5AAAAAAAAACAsMQUKwAAAAAAAPC7PgbTpwZThGGcFeL1me7nLSVbDWsEwgsjyAEAAAAAAAAAYYkOcgAAAAAAAABAWKKDHAAAAAAAAAAQlpiDHAAAAI5kOZxPtClGZITiqI+oZl5/KO7TlqCvw9dPbYDyOJ7TY/0Zc9gCAIAWhM+9AAAAAAAAAICwxAhyeIk3iLEbrXTYoI5v/JBHG4OY72zKjxjUUWMQU25T7jKoI9dmNNEyRuoAAAAAAAAAfsUIcgAAAAAAAABAWKKDHAAAAAAAAAAQluggBwAAAAAAAACEJeYgBwAAAAAAgLE+NvfQcsp09GaEX9fq//qCtV67e8XVMT1uW7gHGsIMI8gBAAAAAAAAAGGJDnIAAAAAAAAAQFiigxwAAAAAAAAAEJaYgxxeygxi4mzK3X7I44hBTLRBTLxNuckLINYgpsamvMqgDjtnGswVtp55wgAAAAAAAABjjCAHAAAAAAAAAIQlOsgBAAAAAAAAAGGJKVYAAABamCyDablamgiH8VZAsmj6dTjhdB85jfeF09E6tQGuPxQ53QZfttnpfnX6HrOVaQABAEAIawmfGQEAAAAAAAAAcIwOcgAAAAAAAABAWGKKFQAAAAAAABjz92hL02m9TONM8zOtr8YwznR6tSjDONP8TNdrWl8/w6m0NjOFFloIRpADAAAAAAAAAMISHeQAAAAAAAAAgLDEFCvwUh3sBP5/RwxiTC5JaueHOkxi7PZbnEEdh23K4w3qONPgMqhog3rsfMxlVAAAAAAAAGgBGEEOAAAAAAAAAAhLdJADAAAAAAAAAMISHeQAAAAAAAAAgLBEBzkAAAAAAAAAICzRQQ4AAAAAAAAACEutgp0AAAAATi4rOyvYKTQ5p6M4LIfxEQ7jfeE0p0BrinwCvV+dnhe+5BNqx60lcPoetrVka4AyAQAAOBEd5AAAAAAAAFA/P/8o7+8fTk1/KPX3ek07z0x/ZDXNL1j7z9QAw/PlH/zwiRDHFCsAAAAAADRjDz30kCIiIrwevXr1CnZaAAA0C4wgh5f1Br/qnWnzC2GUwXqibcpN6jBRZVPuMqjjoEGM26Y83qAOu20+bFCHiRqbcrtjI0mX+2FUgd2xkaQV/MoMAAAAGOnTp49WrFjh+b9VK77uAwBgghYTAAAAAIBmrlWrVkpNTQ12GgAANDtMsQIAAAAAQDP3xRdfKD09Xaeddpquu+467d69u8FYt9utyspKrwcAAOGKDnIAAAAAAJqxQYMGacGCBVq6dKkKCwu1c+dOnXfeeaqqqn9iw4KCAiUmJnoeGRkZTZwxAAChgw5yAAAAAACasVGjRunKK69U//79lZubq7/85S8qLy/X66+/Xm98fn6+KioqPI89e/Y0ccYAAIQO5iAHAAAAAKAFSUpK0umnn67t27fXW+5yueRyuZo4KwAAQhMjyAEAAAAAaEEOHjyoHTt2KC0tLdipAAAQ8uggBwAAAACgGbvrrrtUXFysXbt26eOPP9bll1+uqKgoXXPNNcFODQCAkMcUK3As1g91VNuUR/lhHZL9CZ5kUEeNQcw3NuX13xrHm902m+Rhst/sYkwutCw3iPnOIMbOT7OzTlr+cclWP6wFAAAAaN6+/PJLXXPNNTpw4IA6dOigc889V2vXrlWHDh2CnRpCRD+b71Z1Ivy8XtNOJ9P1msb5ezSoaX2Wn+NaG8aZ9qGYfk+vNYwzPb4DDc8/0/w20RcAP6ODHAAAoAllGX5BOF6oXfIXavk0BV+22eTH7eP5u1OisUy/vDf2OU443UeBzicUmXZqHM/p+e003mlOvrxPbg3zzpKFCxcGOwUAAJqtcPx+AwAAAAAAAAAAHeQAAOB7Dz30kCIiIrwevXr1CnZaAAAAAAAEDFOsAAAAjz59+mjFihWe/1u14qMCAAAAAKDl4lsvAADwaNWqlVJTU4OdBgAAAAAATYIpVgAAgMcXX3yh9PR0nXbaabruuuu0e/fuBmPdbrcqKyu9HgAAAAAANCd0kAMAAEnSoEGDtGDBAi1dulSFhYXauXOnzjvvPFVVVdUbX1BQoMTERM8jIyOjiTMGAAAAAKBx6CAHAACSpFGjRunKK69U//79lZubq7/85S8qLy/X66+/Xm98fn6+KioqPI89e/Y0ccYAAAAAADQOc5DDsVA5aaqbaD2xBjHxNuX1j710JtogxuWHemr8lIs/hMq5BoSrpKQknX766dq+fXu95S6XSy6XyTsPAAAAAAChif4nAABQr4MHD2rHjh264YYbgp0KAABAWOmXnRWU9UYYxplOR2Ban787p1r7eb3fGcZZhnFRhnGmg9NMj0etYdwxwzjT7TU9D0wNMHx9mOZnyrS+zSVb/bxmBBpTrAAAAEnSXXfdpeLiYu3atUsff/yxLr/8ckVFRemaa64JdmoAAAAAAAQEI8gBAIAk6csvv9Q111yjAwcOqEOHDjr33HO1du1adejQIdipAQAAAAAQEHSQAwAASdLChQuDnUKzlNUEl0CbXg5bJ9Q+4Pn7str6OL2E1mm8yT06GstpTk73a6Dr94XT/er08lenrx1f1mF6mbyvmuLcc7qfAn0ZMpc5AwCApsRnDwAAAAAAAABAWKKDHAAAAAAAAAAQluggBwAAAAAAAACEJTrIAQAAAAAAAABhKdTu4YRm4LBNebRBHXYnntswFzt29ZQb1BFrEFNlU+4yqMNuv/qL3Y2eTG40VW0Q810j8zCNAQAAAAAAAHzFCHIAAAAAAAAAQFhiBDkAAAAAAEAT6JOdFZT1RhjGmY6iNLnyOBBxtYZxptvh706xYG2v6VX4rf0cZ3fleB3T7Y0xjDO92tw0zjKMM30dDTR8nZvm94+SrYaR8BUjyAEAAAAAAAAAYYkOcgAAAAAAAABAWKKDHAAAAAAAAAAQluggBwAAAAAAAACEJTrIAQAAAAAAAABhyfENe1evXq3HH39cJSUl2rdvn5YsWaLLLrvMU25Zlh588EG9+OKLKi8v1znnnKPCwkL16NHDn3kjiNbZ3D13kMHdel025fEGeVQbxJjeUbmx67HbniSDOqJtyk32yX6DmMbmIZndadmunjiDOsoNYgDA37IM7zrvK19GJzh9TpTDeJP39cbUX+sw3hdO1+F0GyyH8ZIU4TDe6XF2us2Bzkdyfi4FerSOL/U73U+B1hTnaqBfo4E+tyXn791bbb7TAACA8OH4M+OhQ4c0YMAAzZkzp97yxx57TM8884yee+45rVu3Tqeccopyc3N19OjRRicLAAAAAAAAAIC/OB5BPmrUKI0aNareMsuyNHv2bN13330aM2aMJOmPf/yjUlJS9NZbb2ncuHGNyxYAAAAAAAAAAD/x61WNO3fuVGlpqUaMGOFZlpiYqEGDBmnNmjX+XBUAAAAAAAAAAI3ieAT5yZSWlkqSUlJSvJanpKR4yn7M7XbL7XZ7/q+srPRnSgAAAAAAAAHVx3AefNNRiv6+H4Jpfab3PTC5d5Vkvr2m6zW9z4Lpek3veWC6XtM403t2mMY5vV+Fv/j7+JreR87f57O/j5sp0/x+Yvj+YprfJu7DcYJA3xfHVkFBgRITEz2PjIyMYKcEAAAAAAAAAAgDfu0gT01NlSSVlZV5LS8rK/OU/Vh+fr4qKio8jz179vgzJQAAAAAAAAAA6uXXDvKuXbsqNTVVK1eu9CyrrKzUunXrlJOTU+9zXC6XEhISvB4AAAAAAAAAAASa4znIDx48qO3bt3v+37lzpzZu3Kjk5GRlZmZq6tSpevjhh9WjRw917dpV999/v9LT03XZZZf5M28AAAAAAAAAABrFcQf5+vXrdf7553v+nz59uiRp/PjxWrBgge6++24dOnRIEydOVHl5uc4991wtXbpUMTEx/ssaIS3OICbWptxlUMdhg5hqm/KOBnVUGcSU25SX2ZRLUpJNucnNG0xi7G7asN+gDpMbstjdTSDdoI4DBjEAAAAAAACArxx3kA8dOlSW1fD9XSMiIjRz5kzNnDmzUYkBAAAAAAAAABBIfp2DHAAAAAAAAACA5sLxCHIAAAD8IBRHG9Q6jDeZout4rR3GH3MYL9lPCfZjoXgcnHJ63Jxq+BrQ+jk9Br5wus1NcZyd7qeIANcfikLxuLWE9wAAABAcfI4AAAAAAAAAAIQlRpADAAAAAAA0Q6ZXsZhe/WU6itL0ShJ/j8o0rS/aMM50/7UzjGurLUZxR9XHKO5bw/WaXvV1xDDOlL/3s2kn5XeGcab7xTTOdDtMrxZriqv16tMvO8sobnPJ1gBnEjoYQQ4AAAAAQIhavXq1Ro8erfT0dEVEROitt97yKrcsSw888IDS0tIUGxurESNG6IsvvghOsgAANEN0kAMAAAAAEKIOHTqkAQMGaM6cOfWWP/bYY3rmmWf03HPPad26dTrllFOUm5uro0ePNnGmAAA0T0yxAgAAAABAiBo1apRGjRpVb5llWZo9e7buu+8+jRkzRpL0xz/+USkpKXrrrbc0bty4pkwVAIBmiQ5y+F2UQUy1TXlTnZj7DGLcBjF22+wyqMNun5jMTWWy7zvalB8wqCPOICbeptxueyUpySAGAAAACFc7d+5UaWmpRowY4VmWmJioQYMGac2aNQ12kLvdbrndP3zTqaysDHiuAACEKqZYAQAAAACgGSotLZUkpaSkeC1PSUnxlNWnoKBAiYmJnkdGRkZA8wQAIJTRQQ4AAAAAQBjJz89XRUWF57Fnz55gpwQAQNDQQQ4AAAAAQDOUmpoqSSorK/NaXlZW5imrj8vlUkJCgtcDAIBwRQc5AAAAAADNUNeuXZWamqqVK1d6llVWVmrdunXKyckJYmYAADQf3KQTAAAAAIAQdfDgQW3fvt3z/86dO7Vx40YlJycrMzNTU6dO1cMPP6wePXqoa9euuv/++5Wenq7LLrsseEkDANCM0EEOAADQhJri8j3LYbzTnKIDXL8kHXEY73Sb4x3Gd3AYL0lfOYw/6jA+IsDxTvepJLkcxlc7jK9xGO8Lp+erL/vJidoA198UnG5DS9hmf1q/fr3OP/98z//Tp0+XJI0fP14LFizQ3XffrUOHDmnixIkqLy/Xueeeq6VLlyomJiZYKYcl0/cO0/di07gow7hgdf6YnoWmr3vT7YgzjBuoLUZxgw3rO9UwLsJwvZ8Y1rdcfYzi9hrWZ8r0/DM9vqafMVsbxn1nGOf2c32mr1/T/WK6XtPPJLSzJ6KDHAAAAACAEDV06FBZVsPdHhEREZo5c6ZmzpzZhFkBANBy0EEOv1tRstU25vLsrJOWm/xqaPJLpd0opo4GdZQbxFQ1Mg9JSrIpjzWow+RXcrtcTUZmmRyfcpvydn5aDwAAAAAAAOArbtIJAAAAAAAAAAhLdJADAAAAAAAAAMISHeQAAAAAAAAAgLBEBzkAAAAAAAAAICzRQQ4AAAAAAAAACEt0kAMAAAAAAAAAwhId5AAAAAAAAACAsNQq2AkAAAAAAAAgcExHR5rGRRvGRRnGtTGMi9cWo7iBhvUNMow73TDOMoxzG8b9zDCujeF+WaI+RnH7DNdrur2m54FpfaZxEYZxpue96XbUGMb5e3vhOzrIERTf2ZTHGtSR4of1mLwATN7YkmzKq/2wnhEGdWw1iNloU97FoI5yg5gMm/Ikgzr2GMQAAAAAAAAAvqKDHAAAtGhZ2VmO4kNx/jnT0Sp1agOSxQ+OBLh+SXI5jHd63NIcxs9wGC/JcCzXDxb5sA4njjmM3+3DOgJ97jXF69Pp6810lFgd09FsdULxPcnpcQ70eQEAANAYofh5CwAAAAAAAACAgKODHACAMLB69WqNHj1a6enpioiI0FtvveVVblmWHnjgAaWlpSk2NlYjRozQF198EZxkAQAAAABoInSQAwAQBg4dOqQBAwZozpw59ZY/9thjeuaZZ/Tcc89p3bp1OuWUU5Sbm6ujR482caYAAAAAADQd5iAHACAMjBo1SqNGjaq3zLIszZ49W/fdd5/GjBkjSfrjH/+olJQUvfXWWxo3blxTpgoAAAAAQJNhBDkAAGFu586dKi0t1YgRIzzLEhMTNWjQIK1Zs6bB57ndblVWVno9AAAAAABoTuggBwAgzJWWlkqSUlJSvJanpKR4yupTUFCgxMREzyMjIyOgeQIAAAAA4G9MsYKgOGhT3tWgjn4GMettyssN6og2iGlvU77ToI44m/KNBnXEG8R0tCk3eVOIMoiJtSnfY1DHCJvybtlZtnXkl2w1WBMAX+Tn52v69Ome/ysrK+kkBwAAAAA0K3SQAwAQ5lJTUyVJZWVlSktL8ywvKyvTGWec0eDzXC6XXC5XoNMDAABoMSzDONPOGtO4CMM402kGEgzjsrXFKG6AYX0mg7Uk6RTDuJ6Gcf81jNtnGFdhGPcPw7jTDePGGB6PIvUxiqsyXG+wpq8w/aZyzM/rNd1e0/cDU6av8y0MJDwBU6wAABDmunbtqtTUVK1cudKzrLKyUuvWrVNOTk4QMwMAAAAAILAYQQ4AQBg4ePCgtm/f7vl/586d2rhxo5KTk5WZmampU6fq4YcfVo8ePdS1a1fdf//9Sk9P12WXXRa8pAEAAAAACDA6yAEACAPr16/X+eef7/m/bu7w8ePHa8GCBbr77rt16NAhTZw4UeXl5Tr33HO1dOlSxcTEBCtlAAAAAAACjg5yAADCwNChQ2VZDc9yFxERoZkzZ2rmzJlNmJVzWQY35/2xQM8n57R+07kBG8NpTrWG81H6qrcPzznXYXy543izuTXrbHNYvyRd4TA+zT7Ey5cO4y93GP+0w3hJesdhfKXDeKd3Pah2GC9JtT48xwl/zzf6Y6bz8x6vpgnWEWiBPm4AAKDlYg5yAAAAAAAAAEBYooMcAAAAAAAAABCW6CAHAAAAAAAAAIQl5iBHUETblO8xqOOwH/JIMYgpN4ixy6WtQR2dbMrLDOrYaxDTzqY83qAOk/k87eamzDCoY7tN+VaDOgAAAAAAAICGMIIcAAAAAAAAABCWGEEOAAAAAABQjz7ZWX6tL8LPcXZXZ9ex/FzfEG0xirvEsL7WhnGm2+E2jKsyjCs2jDO52lqSOhjGmVxJLkkVhnHDDePaGx7fKvUxiqs1XK/pKF7T10eNYZy/BWu9pq8PnIgR5AAAAAAAAACAsEQHOQAAAAAAAAAgLNFBDgAAAAAAAAAIS3SQAwAAAAAAAADCEh3kAAAAAAAAAICwRAc5AAAAAAAAACAstQp2AghPcTblhw3qyDCIifVDHUcMYrbblHcxqKPMpryfQR1ZBjF/sSlPMahjr0FMlU15tUEddsfvfIM6WmXb75U/lmw1qAkAGrLFUbTlwxpqfHhOKPnCh+d87TA+ymF8gsPj9q3D+iWpr8P4burjKL63w/rXOoy/yGG8ZN/+/5jd55LG8mU0kNPXW4TDeF/eA5yoDXD9kvNtcHocmmIbAAAA6jCCHAAAAAAAAAAQluggBwAAAAAAAACEJaZYAQAAAAAAYaWPwVSMkvmoQtPplkynBDPtrDHNzzSuh+EUZOcY1pdpGLffMO4/hnEmU6VK9tN61jlkGHeKYVx7w7ifGMbZTWNb53TDuEsM4140jDOZRlcyP0/9Pf2g6evNdIqx73xNpJGYosx3jCAHAAAAACBErV69WqNHj1Z6eroiIiL01ltveZXfdNNNioiI8HqMHDkyOMkCANAM0UEOAAAAAECIOnTokAYMGKA5c+Y0GDNy5Ejt27fP83j11VebMEMAAJo3plgBAAAAACBEjRo1SqNGjTppjMvlUmpqahNlBABAy8IIcgAAAAAAmrFVq1apY8eO6tmzp26//XYdOHDgpPFut1uVlZVeDwAAwhUjyBEU0TblboM6Mgxi/mtTbnIjC5dBTJJNeZVBHXY3ETHJY49BTD+bcpObxvQ0iFlnU25yMxS7Y3yeQR07DWIAAACA5mrkyJEaO3asunbtqh07dujee+/VqFGjtGbNGkVF1f/pvqCgQDNmzGjiTAEACE10kAMAAAAA0EyNGzfO83e/fv3Uv39/devWTatWrdLw4cPrfU5+fr6mT5/u+b+yslIZGSZDkAAAaHmYYgUAAAAAgBbitNNOU/v27bV9+/YGY1wulxISErweAACEKzrIAQAAAABoIb788ksdOHBAaWlpwU4FAIBmgSlWAAAAjuN09ECEw3jLYbwvaptgHU7Y3WfDX89xorXD+CQf1rHBYfzPtMVRvNNt+Kv6OIr3ZbKFMx3Gf+Aw3um5bXJvlR877DDe6Ws60O8ZvrzHOM3J6fuk0+PGKC5vBw8e9BoNvnPnTm3cuFHJyclKTk7WjBkzlJeXp9TUVO3YsUN33323unfvrtzc3CBmDQBA80EHOQAAAAAAIWr9+vU6//zzPf/XzR0+fvx4FRYWatOmTXrppZdUXl6u9PR0XXjhhfrtb38rl8sVrJRxEqY/AJkevWjDONMfDLMM42IM40y1NYwrM4z7h2Gc6Y/N/Q3jOhvGmU5qZPrTtmnnXpJhnOl+WWQYd9AwrsYwzvSH3Wo/r9f0R2h/D4gx3V5+YPYdHeQAAAAAAISooUOHyrIa7m5ZtmxZE2YDAEDLw48LAAAAAAAAAICwRAc5AAAAAAAAACAsMcUKgsJuXrN1BnXsNYixu7mUyXxtJnNWxduUlxvU0camvMqgDpOYjjbl/zKow257Jfs57n5qUEeSTfkugzq6G8QAAAAAAAAgPDGCHAAAAAAAAAAQluggBwAAAAAAAACEJTrIAQAAAAAAAABhiQ5yAAAAAAAAAEBYooMcAAAAAAAAABCWWgU7AQAAAAAAAH/Iys4yijMdLRjh57gow7haw7hjhnGnGMadahhXbhj3lmHcmYZxXxvGtTWMO+rnuCrDuFjDONPj6+/tOGgYZxnGmaoxjDN9HZl2epqu15RpfaZx/t7POBEd5AAAIGhMv8TWaYpL3yK0JaD1m37hbUqBzqkpjpvTbXA7jDf9Qn683zmMf9FhfDuH8fscnts9HNYvSf+jPo7if+Kw/l0O450eZ8n5l2Rf1uGE03Pbl9eb0y/e/u5ICAYn7U9NTY22bdwWwGwAAEAwMcUKAAAAAAAAACAsORpBXlBQoMWLF+vzzz9XbGysfvrTn+p3v/udevbs6Yk5evSo7rzzTi1cuFBut1u5ubmaO3euUlJS/J48mq9dfqjjgB/qMLn8yeTSnUE25XsM6vDHSByTXO32W7xBHd/5IZetBnXE2ZSbjPuJNoiZZjOC6KkSk2wBAAAAAADQ3DgaQV5cXKxJkyZp7dq1Wr58uY4dO6YLL7xQhw4d8sRMmzZN77zzjhYtWqTi4mLt3btXY8eO9XviAAAAAAAAAAA0hqMR5EuXLvX6f8GCBerYsaNKSko0ZMgQVVRUaN68eSoqKtKwYcMkSfPnz1fv3r21du1aDR482H+ZAwAAAAAAAADQCI2ag7yiokKSlJycLEkqKSnRsWPHNGLECE9Mr169lJmZqTVr1tRbh9vtVmVlpdcDAAAAAAAAAIBA87mDvLa2VlOnTtU555yjvn37SpJKS0sVHR2tpKQkr9iUlBSVlpbWW09BQYESExM9j4yMDF9TAgAAAAAAAADAmM8d5JMmTdJnn32mhQsXNiqB/Px8VVRUeB579pjczhAAAAAAAAAAgMZxNAd5ncmTJ+vdd9/V6tWr1alTJ8/y1NRUVVdXq7y83GsUeVlZmVJTU+uty+VyyeVy+ZIGAAAAAAAAAAA+c9RBblmWpkyZoiVLlmjVqlXq2rWrV3l2drZat26tlStXKi8vT5K0bds27d69Wzk5Of7LGgAAAAAA4Ee2lmw1iuuXnWUUF2G43kbd4K0elmFcjWGcaedPtGHcLsO4CsO4Y4ZxKYZxPQzjlhvGHTCMa20YZ+pUw7ivDePqn/z4RNWGcf6+i6C/z/vvDONMX+em+fm7PlObDd//cCJHHeSTJk1SUVGR3n77bcXHx3vmFU9MTFRsbKwSExM1YcIETZ8+XcnJyUpISNCUKVOUk5OjwYMHB2QD0DyV25RHGdTRziDGrp5dBnV0NIjZbFNuMnGQ3ez7ZQZ1dDeIKbcpN/lAdJEf1nPYoA67DyHrDeo44of1AAAAAAAAoGVy1EFeWFgoSRo6dKjX8vnz5+umm26SJD311FOKjIxUXl6e3G63cnNzNXfuXL8kCwAAAAAAAACAvzieYsVOTEyM5syZozlz5vicFAAAaL56ntFTUVEm1wI1DeeXPPdxGL/F8Rqcqg1w/U73kdN8Ap1/U/BlG9wO402uFmtMvOnlvnV2OoyXpO0OXw/9HdYf6/D1uc9h/ZJ00GG86eXbvvL35df+4PT14O+pJ/yxjpbwvgQAAPyjKT6rAAAAAAAAAAAQcuggBwAgDKxevVqjR49Wenq6IiIi9NZbb3mV33TTTYqIiPB6jBw5MjjJAgAAAADQROggBwAgDBw6dEgDBgw46RRoI0eO1L59+zyPV199tQkzBAAAAACg6TmagxwAADRPo0aN0qhRo04a43K5lJqa2kQZAQAAAAAQfIwgBwAAkqRVq1apY8eO6tmzp26//XYdOHDgpPFut1uVlZVeDwAAAAAAmhM6yAEAgEaOHKk//vGPWrlypX73u9+puLhYo0aNUk1NTYPPKSgoUGJioueRkZHRhBkDAAAAANB4TLGCoIi3Ke9mUEe5H/Jo56f1ZNmUuw3qaLgLytw3BjE9bcoPG9QRaxCzwqa8n0Eddseno0EdVQYx3xnEAC3duHHjPH/369dP/fv3V7du3bRq1SoNHz683ufk5+dr+vTpnv8rKyvpJAcAAC1KrWGcaedKtJ/jTL/LmNbX1jDuNMM40/yiDOPs+hKc1me6XyoM41yGcab7Jckwrrdh3MmvD/3BYsM4k/6DYDI9D44Zxpm+H/h7NLI/+otwcowgBwAAJzjttNPUvn17bd++vcEYl8ulhIQErwcAAAAAAM0JHeQAAOAEX375pQ4cOKC0tLRgpwIAAAAAQMAwxQoAAGHg4MGDXqPBd+7cqY0bNyo5OVnJycmaMWOG8vLylJqaqh07dujuu+9W9+7dlZubG8SsAQAAAAAILDrIAQAIA+vXr9f555/v+b9u7vDx48ersLBQmzZt0ksvvaTy8nKlp6frwgsv1G9/+1u5XKYzKQIAAAAA0PzQQQ4AQBgYOnSoLMtqsHzZsmVNmA0AAAAAAKGBDnIAAIDjBPoGLb7chT7QOdUGuH5fON1mp9sQ6Pp9fU4o+daH5zzsMN7pPkrSFkfxZzisX5KuVB9H8Usd1v83h/FORQS4fsn568dpTg3/nAsAAOB/3KQTAAAAAAAAABCWGEGOoGhjU37AD3VI9qP0uhjUscYg5kOb8jiDOpJsyssN6jhiELPfprybQR37DGKiDGLsHLYp32VQx3kGMXZvhJ9mZ9nWMbBkq8GaAAAAAAAAEEoYQQ4AAAAAAAAACEuMIAcAAAAAAC1CH4OrPyXzeyCYjio0nTvfdL3VhnGmnTrHDONM7xkQbxjX0TDua8O43YZxpvfRsLvC2qmDhnEVhnH/NIy70DDObRi3xzDO9HwxPU9NXx+m9/QxXe93hnH+zs8Uo5sDj30MAAAAAECIKigo0FlnnaX4+Hh17NhRl112mbZt2+YVc/ToUU2aNEnt2rVTmzZtlJeXp7KysiBlDABA80IHOQAAAAAAIaq4uFiTJk3S2rVrtXz5ch07dkwXXnihDh065ImZNm2a3nnnHS1atEjFxcXau3evxo4dG8SsAQBoPphiBQAAAACAELV06VKv/xcsWKCOHTuqpKREQ4YMUUVFhebNm6eioiINGzZMkjR//nz17t1ba9eu1eDBg4ORNgAAzQYjyAEAAAAAaCYqKr6fvTg5OVmSVFJSomPHjmnEiBGemF69eikzM1Nr1qyptw63263KykqvBwAA4YoOcgAAAAAAmoHa2lpNnTpV55xzjvr27StJKi0tVXR0tJKSkrxiU1JSVFpaWm89BQUFSkxM9DwyMjICnToAACGLDnIAAAAAAJqBSZMm6bPPPtPChQsbVU9+fr4qKio8jz179vgpQwAAmh/mIEdQxNmURxnUUe6H9ewyqCPaIMZOO4MYt015tZ/WY/eiP9+gjvUGMfE25UcM6rDL1e74StIyg5iuNuV7DeoAAAAAAmny5Ml69913tXr1anXq1MmzPDU1VdXV1SovL/caRV5WVqbU1NR663K5XHK5XIFOGQCAZoEOcgAA0KJFOH7GFkfRtY7rD7xQzCnQnF4W2RT7yGlOoXZp5zEfnvNfv2fRuPq/9mEdkQ7fA6rUx1G80y9gvhyHUGM1wTqcvqadvN6aIv+Trt+yNGXKFC1ZskSrVq1S167ewzuys7PVunVrrVy5Unl5eZKkbdu2affu3crJyQlGygAANCt0kAMAAAAAEKImTZqkoqIivf3224qPj/fMK56YmKjY2FglJiZqwoQJmj59upKTk5WQkKApU6YoJydHgwcPDnL2AACEPjrIAQAAAAAIUYWFhZKkoUOHei2fP3++brrpJknSU089pcjISOXl5cntdis3N1dz585t4kxDg+nVAc6vMDs50ysNvjOMM+2sMZmeVJJqDONMr9TpYhh31DDOdHtNpuqUpE2Gcf80jGttGJdlGGe6vabXNKUZxplM3SpJ2w2vjgrWVU6m57PdVLZ1TF+/plcrmb7OETroIAcAAAAAIERZln3XTUxMjObMmaM5c+Y0QUYAALQsoTbVIQAAAAAAAAAATYIOcgAAAAAAAABAWKKDHAAAAAAAAAAQluggBwAAAAAAAACEJW7SiaCItin/iUEd6w1iyg1i7HQxiMmwKTe5w7LJ9jSFDQYxBwxi2tmUdzOo47BNeReDOpYZxNjVU2VQx7XZ9vcrLyrZalATAAAAAAAAmgojyAEAAAAAAAAAYYkOcgAAAAAAAABAWKKDHAAAAAAAAAAQlpiDHAAANBtN8ct+rcN4k/tMNKZ+mAnF/RoV4PqdnntO+fJ6a+0w3ulxcxp/0GG8JC1xGB+jLY7iv1Mfh2twxpcveN/5PYvQ5+RcCsX3FzQd0/fyiIBm0TDTtqDaMG6zYVymYdxGw7gjhnEuwzhT/u4USzWMSzKM+69h3HLDOJP7iUlm9+GSzM97088UpnGWYZwp09eRaRztRvPDCHIAAAAAAAAAQFiigxwAAAAAAAAAEJboIAcAAAAAAAAAhCXmIIffvZydZRuzx6Z8ncF64g1iyv1QR4ZBTJxNucm8dXbrMZ2TzY5dLibrMdmewzblKwzq6GlTbnceSVKZQcxWm/JtBnWYnEtX2Lw23iixywQAAAAAAAD+xAhyAAAAAAAAAEBYooMcAAAAAAAAABCW6CAHAAAAAAAAAIQlOsgBAAAAAAAAAGGJDnIAAAAAAAAAQFhqFewEAAAAAAAA/GFzyVajuH7ZWUZxtYbrDfXRh0cN40rUxyguSVuM4qoM17vXMC7OMO6wYVy8YVx7w7hMw7juhnEjDOP2GMa9ZBhXYRhnGcaZijGMM31dmp73pvWZxkUYxiF0hPp7OAAAAAAAAAAAAcEIcgAA0KI5HQ1Qazhy6gdmI6jq+DI6wXS0SmPW4YTTfJpCKI76CHROTkdtOT1uUQ7jJefbfMyHdQSa0/1kOjrtB87eM+TwPcmX16e/RwACAAA0J3SQw+9MLu05YFNe7Yc6JPsvdikGdZh8iSmzKTe5XMxue3oa1GFy+Vo/my9ZuwzqMLkMLc2mfL9BHXb7LcOgjnKDGLtz1uSN0uQCzc025YsMLvO80vCSUQAAAAAAANgLxcE2AAAAAAAAAAAEHB3kAAAAAAAAAICwRAc5AAAAAAAAACAs0UEOAAAAAAAAAAhLdJADAAAAAAAAAMISHeQAAAAAAAAAgLDUKtgJAAAAAAAANKXNJVuN4vpnZ/l1vbV+rc28Pn/HlRvGmfrOMC7aMC7VMM4yjNtjGLfTMK67YVwnw7hjhnGt1ccorsawvgjDONPz6qif40y3w/T8Mz1fTPeLaX0IPEaQAwAAAAAAAADCEiPI4XfVBjEHbMpNfuVzG8TE25Rv1hbbOkx+SbRbT5VBHXbbbPKLtd1+laQym21OMqgj2uBX5x0G9dj5l015iUEdHQ1i4mzKuxjUccQP61lnUMdLNiNYTM6T+wxHywAAAAAAALR0jCAHAAAAAAAAAIQlRpADAAC/ilRo/QLv77k+f8zptvqST1OsI5B8OR+cbkOg95Ev22A672UdpzlFOYx3ug1O85ecb0Oonau+CLVtMJ33tDHPCfTrjTlZAQBAUwql768AAAAAAAAAADQZOsgBAAgDBQUFOuussxQfH6+OHTvqsssu07Zt27xijh49qkmTJqldu3Zq06aN8vLyVFZWFqSMAQAAAAAIPDrIAQAIA8XFxZo0aZLWrl2r5cuX69ixY7rwwgt16NAhT8y0adP0zjvvaNGiRSouLtbevXs1duzYIGYNAAAAAEBgMQc5AABhYOnSpV7/L1iwQB07dlRJSYmGDBmiiooKzZs3T0VFRRo2bJgkaf78+erdu7fWrl2rwYMHByNtAAAAAAACihHkAACEoYqKCklScnKyJKmkpETHjh3TiBEjPDG9evVSZmam1qxZE5QcAQAAAAAINEaQw+8OGMQk2ZRHG9RRri22MTU25YcN1pNkEHPEpnyfn9ZjJ8ogxi5Xu/LvY+z3fbT6nLTcZbCejjblbQzqMHmTs9tv+w3q6OKH9Qw3qMNOkkHMXdlZtjFPlGxtdC4IXbW1tZo6darOOecc9e3bV5JUWlqq6OhoJSUlecWmpKSotLS03nrcbrfcbrfn/8rKyoDlDAAAEAxWkOqz+y7rlMl3bEk6ahj3H8O4cwzjTL7LStJZhnGZhnGm+/kNwzjT4xtnGLfLMG6+YdwOwzhTJt/rJemYYVy1YVytYZzp8YgwjDNlWp9pfqbb28fgu74kbeH7/gkYQQ4AQJiZNGmSPvvsMy1cuLBR9RQUFCgxMdHzyMjI8FOGAACgjsmNtocOHaqIiAivx2233RakjAEAaF7oIAcAIIxMnjxZ7777rj744AN16tTJszw1NVXV1dUqLy/3ii8rK1Nqamq9deXn56uiosLz2LNnTyBTBwAgLJncaFuSbrnlFu3bt8/zeOyxx4KUMQAAzQtTrAAAEAYsy9KUKVO0ZMkSrVq1Sl27dvUqz87OVuvWrbVy5Url5eVJkrZt26bdu3crJyen3jpdLpdcLtMLKwEAgC/sbrRdJy4ursEftQEAQMMYQQ4AQBiYNGmSXn75ZRUVFSk+Pl6lpaUqLS3VkSPf33kgMTFREyZM0PTp0/XBBx+opKREN998s3JycjR48OAgZw8AAOr8+EbbdV555RW1b99effv2VX5+vg4fbviOS263W5WVlV4PAADCFSPIAQAIA4WFhZK+n6P0ePPnz9dNN90kSXrqqacUGRmpvLw8ud1u5ebmau7cuU2cKQAAaEh9N9qWpGuvvVadO3dWenq6Nm3apHvuuUfbtm3T4sWL662noKBAM2bMaKq0AQAIaXSQAwAQBizL/h7pMTExmjNnjubMmdMEGQEAAKfqbrT90UcfeS2fOHGi5+9+/fopLS1Nw4cP144dO9StW7cT6snPz9f06dM9/1dWVnKzbQBA2KKDHAAA+FWtpIhgJ3Ecp/PJRTmMr3EY3xQCPYdeoPepJB3z4TlONMU8g7VNsI5Aau75S75tg9Nzw+k6Av36iXYYL0nVDuNbwrnh5DjY/8TcNOputL169WqvG23XZ9CgQZKk7du319tBzn1EAAD4AR3kAAAAAACEKLsbbddn48aNkqS0tLQAZwcAQPPnqIO8sLBQhYWF2rVrlySpT58+euCBBzRq1ChJ0tGjR3XnnXdq4cKFXnOXpqSk+D1xBM+T2VknLd/mh3XEGsRU+WE9Dd+25gfxflhPG4MYu9E+boM6TPbbfpvyOIM6DhjlsuWk5e3Ux6CWkzN5A0syiLE7xiYXm3YxiPnAptxke+xGkZmMA+piEPNLm9d5T4M67ijZahAFAACAk5k0aZKKior09ttve260LX1/g+3Y2Fjt2LFDRUVFuuiii9SuXTtt2rRJ06ZN05AhQ9S/f/8gZw8AQOhz1EHeqVMnzZo1Sz169JBlWXrppZc0ZswYbdiwQX369NG0adP03nvvadGiRUpMTNTkyZM1duxY/e1vfwtU/gAAAAAAtFh2N9qOjo7WihUrNHv2bB06dEgZGRnKy8vTfffdF4RsW57NhoM+BtgMMHHqO8M4X6Yx88d69xgOePqrYX1dbAZY1Uk1rM/02ol/GsbF+Hm9plM3fWQfIkn6p+HxMJ0a0DQ/0ynxTNdrGmd6nppO+2g67ZbpekNlai6Yc9RBPnr0aK//H3nkERUWFmrt2rXq1KmT5s2bp6KiIg0bNkzS9w127969tXbtWg0ePNh/WQMAAAAAEAbsbrSdkZGh4uLiJsoGAICWx+f7E9XU1GjhwoU6dOiQcnJyVFJSomPHjmnEiBGemF69eikzM1Nr1qxpsB63263KykqvBwAAAAAAAAAAgea4g3zz5s1q06aNXC6XbrvtNi1ZskRZWVkqLS1VdHS0kpKSvOJTUlI8c6TVp6CgQImJiZ5HRobJbL8AAAAAAAAAADSO4w7ynj17auPGjVq3bp1uv/12jR8/Xlu3+n4jtvz8fFVUVHgee/bs8bkuAAAAAAAAAABMOZqDXJKio6PVvXt3SVJ2drY++eQTPf3007r66qtVXV2t8vJyr1HkZWVlSk1t+DYKLpdLLpfLeeYAAAAAAAAAADSCz3OQ16mtrZXb7VZ2drZat26tlStXesq2bdum3bt3Kycnp7GrAQAAAAAAAADArxyNIM/Pz9eoUaOUmZmpqqoqFRUVadWqVVq2bJkSExM1YcIETZ8+XcnJyUpISNCUKVOUk5OjwYMHByp/BME3NuXVBnXE29axxbYOk+sOomzKyw3qqDKISbcp72JQxzab8hqDOuIMYqINYvzBbr+ZbM9hm3KTc81ke+3OR5M6TM4Tu23ea1DHeTblGw3q6GkQs9+mfLNBHS9mZ9nG3FLi+xRdAAAAAAAAjeWog3z//v268cYbtW/fPiUmJqp///5atmyZLrjgAknSU089pcjISOXl5cntdis3N1dz584NSOIAAAAAAAAAADSGow7yefPmnbQ8JiZGc+bM0Zw5cxqVFAAAgL9EBDuBH/Flfrtav2fROK0dxjfFNh9zGN/oeQYDwGlOJldjNab+UBRqr4WmYIXgOpy+rzqt35fj7PQ5W7mKDQAA/P8c36QTAAAAAAAAP/iH4Y8uPzGYhtAJ0x9LvzOMM/2xyW0Yd8QwLlJ9jOLKDKZjlaRDhutdbBjXxTDOdILhDYZxyw33i930pHVMzxfT88D0vDJdr78Htpj+QGu6HaZMtyPUBvKEs5YwkAQAAAAAAAAAAMfoIAcAAAAAAAAAhCU6yAEAAAAAAAAAYYkOcgAAAAAAAABAWKKDHAAAAAAAAAAQlloFOwE0P/v9UEcbmztPf2lQR6xBTLlNebVBHSbs7uBtl4ckxdmUm+Rqcodwu7tHm6wn2g+5VBvcfTze5o7dPzHII8ogZpdN+R6DOuyOn2R/53OT/VpiU366QR1bDWJG25R/bFDHXwxiLs/OOmn5khKTbAEAAAAAAHzDCHIAAAAAAAAAQFiigxwAAAAAAAAAEJboIAcAAAAAAAAAhCXmIAcAAAAAAGgCGwzvsTPQ5l49dUzuuSRJ3xnGxRjGmbIM43Ybxj1jc5+qOskG97ySpFTD9drdd6yO2Vql9w23o9ywPtP9bNoJeMwwLsIwzvQ8PWoYZ7q9pnH+Fur54UR0kAMAADSh2mAn4AemX5oaw/QLV51AXxbpy3FzmlOgzw2n9TfFuRqKl7M63e5Q2wZfbkLv9PXm9At9KJ57AAAAdULt8xwAAAAAAAAAAE2CDnIAAAAAAAAAQFiigxwAAAAAAAAAEJaYgxx+V2UQY3eDBpMbYLQziLGbg9EkV5dBTLlNuUmu/hBrEHPYprzcT+uJtylPMqjD7g3K5DwxWY9djMnxO2gQY3c7nnSDOvbalCcZ1GESY8fklkErDGLeMbxJEQAAAAAAQCAwghwAAAAAAAAAEJboIAcAAAAAAAAAhCU6yAEAAAAAAAAAYYkOcgAAAAAAAABAWOImnQAAAAAAACHkOz/HRRvGJRrG1RjGHTaMM1VuGFehPkZxXxvW90/DuA8N444axpkyPQ9MRRnGHfPzek3PK39vb4RhnOXn9SJ0MIIcAAAAAAAAABCW6CAHAAAAAAAAAIQlpliBl2nZWY2uo9wgJsmm3OSymgMGMR1tysv9UIckVflhPSk25W6DOkzYXVoXa1BHtUGM3ZvLEYM67HI1OU/+ZRBjd4xdBnWYxNjlG29QRz+b8jiDOkzOJbvXl0kdNDAIhFofnmN6iWgdp6MHfMkp1ITiNgd6FEcojhIJ9H51us1NsY84DvZML/v2tX5fhOL73taSrcFOAQAANFOh+JkUAAAAAAAAAICAo4McAAAAAAAAABCW6CAHAAAAAAAAAIQlOsgBAAAAAAAAAGGJDnIAAAAAAAAAQFiigxwAAAAAAAAAEJZaBTsBAAAAAAAA/GBTyVajuJ9kZxnFtTZc7zeGcRGGcaajMk07p2oM46IN46oM40y313Q7ogzjjhrG1RrGmR6P7wzjTI+HaZzpei0/x5keX9M40+31ty2G7xs4ESPIAQAAAAAIUYWFherfv78SEhKUkJCgnJwcvf/++57yo0ePatKkSWrXrp3atGmjvLw8lZWVBTFjAACaF0aQw8thg5g4m/Ikgzp62pTvNKjjgEHMfptyk19tTX5RdtmUm+xXu/VUG9TR0SDG7pdMu2MjSXsNYuzOk3KDOuyOn0kd3QxiMmzK7Y6vJPUziLEbjXHEoI40m/JdBnXY7VdJyrYpH8gv0wAAAE2iU6dOmjVrlnr06CHLsvTSSy9pzJgx2rBhg/r06aNp06bpvffe06JFi5SYmKjJkydr7Nix+tvf/hbs1AEAaBboIAcAAAAAIESNHj3a6/9HHnlEhYWFWrt2rTp16qR58+apqKhIw4YNkyTNnz9fvXv31tq1azV48OBgpAwAQLPCFCsAAAAAADQDNTU1WrhwoQ4dOqScnByVlJTo2LFjGjFihCemV69eyszM1Jo1axqsx+12q7Ky0usBAEC4ooMcAAAAAIAQtnnzZrVp00Yul0u33XablixZoqysLJWWlio6OlpJSUle8SkpKSotLW2wvoKCAiUmJnoeGRl2kw4CANByMcUKAADwq20btxnHZmVnBTCT75nevb5ObYDrD0XfOYxvihEWTo9DqNXvi1AbueJLPk73a0s4Dk63IVJ9HMUH+j3MF3b3tPmxUDzOzU3Pnj21ceNGVVRU6I033tD48eNVXFzsc335+fmaPn265//Kyko6yQEAYSvUPocDAIAAKCgo0FlnnaX4+Hh17NhRl112mbZt8+7IHjp0qCIiIrwet912W5AyBgAAdaKjo9W9e3dlZ2eroKBAAwYM0NNPP63U1FRVV1ervLzcK76srEypqakN1udyuZSQkOD1AAAgXNFBDgBAGCguLtakSZO0du1aLV++XMeOHdOFF16oQ4cOecXdcsst2rdvn+fx2GOPBSljAADQkNraWrndbmVnZ6t169ZauXKlp2zbtm3avXu3cnJygpghAADNB1OsAAAQBpYuXer1/4IFC9SxY0eVlJRoyJAhnuVxcXEnHXEGAACaVn5+vkaNGqXMzExVVVWpqKhIq1at0rJly5SYmKgJEyZo+vTpSk5OVkJCgqZMmaKcnBwNHjw42KkDANAs0EEOAEAYqqiokCQlJyd7LX/llVf08ssvKzU1VaNHj9b999+vuLi4YKQIAAAk7d+/XzfeeKP27dunxMRE9e/fX8uWLdMFF1wgSXrqqacUGRmpvLw8ud1u5ebmau7cuUHOGk1lQ8lWo7iBTXDfl/qYdjqZTm8QZRhneu8D0/Wa3g/CNO6Yn+NM7ydTbRhnuv8iDOOc3u+mqZkeN9PtNT2vuEdH6KCDHF7KDWLsbsqTZFDHZpubEx3WFts6qgzWY8dfXT7RNuUmue71Qx6HDWLsPlCY3HTJpFEttymPN6ijm015F4M6TBywKTe5XZHbIGa/Tfkugzrsbn1okus3BjF/MYhB81VbW6upU6fqnHPOUd++fT3Lr732WnXu3Fnp6enatGmT7rnnHm3btk2LFy+utx632y23+4ezv7KyMuC5AwAQbubNm3fS8piYGM2ZM0dz5sxpoowAAGhZ6CAHACDMTJo0SZ999pk++ugjr+UTJ070/N2vXz+lpaVp+PDh2rFjh7p1O/Enq4KCAs2YMSPg+QIAAAAAECjcpBMAgDAyefJkvfvuu/rggw/UqVOnk8YOGjRIkrR9+/Z6y/Pz81VRUeF57Nmzx+/5AgAAAAAQSIwgBwAgDFiWpSlTpmjJkiVatWqVunbtavucjRs3SpLS0tLqLXe5XHK5XP5MEwAAAACAJkUHOQAAYWDSpEkqKirS22+/rfj4eJWWlkqSEhMTFRsbqx07dqioqEgXXXSR2rVrp02bNmnatGkaMmSI+vfvH+TsAQAAAAAIDDrIAQAIA4WFhZKkoUOHei2fP3++brrpJkVHR2vFihWaPXu2Dh06pIyMDOXl5em+++4LQrYAAAAAADQNOsgBAAgDlmWdtDwjI0PFxcVNlA0AAAAAAKGBDnIAABA0W0u2OorPys5yvI7vHD/DmQj1cRQfpS2O11HjML7W8RpCj9NtCMU7z4daToF+LUiBP26heF44/UIVEZAsfnDyn0PrF2rvGU7bBgAAgMaggxxe4g1i0m3K67+Vmze7E89tUEeJQYxdLvsN6ig3iLHL12R77Do/TI5NnEHMf23K/XW7vWrbCPsOJbs6/m6QR5VBTBeb8m0GdbxhEGN3vrUzqONhm3KTr5P5fOkEAAAAAACQRAc5AAAAAABAi/ap4SCZbMOr9aIM12t6FVywrmRxepWev5he1eXvq798ucrIH/x9vgSL6f4LtSuzYC/UrvwEAAAAAAAAAKBJ0EEOAAAAAAAAAAhLdJADAAAAAAAAAMISHeQAAAAAAAAAgLBEBzkAAAAAAAAAICzRQQ4AAAAAAAAACEt0kAMAAAAAAAAAwlKrYCeA0FJtENPRpvyAQR09bSP6GNSyxTai3Ka8ymAtNQYxSTblcQZ1lNmUuw3qaGcQk2RTvtOgDpfRek5+DE3ONbt9YpLHTw1iom3Kyw3qMMnltZKtBlEn90ajawAAAAAAAEAdRpADAAAAAAAAAMISI8gBAECzsdWHKzGysrMcxdc6rL8pRhuE2oiGUNxHgebLNjjdT6GmKfIPxX3k9FhHGF356DvLYbwv+zTQx8GX924AwVFi+HodaPj5KsJwvabvQ8cM40w5fY+1851hnMmV6pL5fjHdz/4+Hqbba7pefx8PU6bba/oZwTTOdL1baEcDriV8XwEAAAAAAAAAwDE6yAEAAAAAAAAAYYkOcgAAAAAAAABAWKKDHAAAAAAAAAAQluggBwAAAAAAAACEpVbBTgBN51cGd5mO9sN6kgxiPrApNzkxuxnEbLYpN7lzdKxBjF09Jutx2ZRHGdRhEmN3jOMN6rDLVZLcNuXlBnXYxZjkmmYQU2JTvtegjte4qzQAAAAAAECzwwhyAAAAAAAAAEBYooMcAAAAAAAAABCW6CAHAAAAAAAAAIQl5iAHAAAAAACAsU8N78GUbXAvNCcsw7jv/FyfKdP6TONqfU2kAaadgP5eb0sRYRhnenwZtRw6OBYAAAAAAAAAgLDECHIAAIDjOB0x43S0genIk8ZwmlOgt7kpBHqkU1OMpArF/Rpogd5m3+rv4+csvPl7tGIwbDUcOQoAANAchOPncAAAAAAAAAAA6CAHAAAAAAAAAISnRk2xMmvWLOXn5+uOO+7Q7NmzJUlHjx7VnXfeqYULF8rtdis3N1dz585VSkqKP/JFI0QbxCQZxOy3KTe5GYbdbTpcBnVsM7j8NcOmvEpbbOuoMcil3CDGjsnxsWOy3w7YlMcZ7Nc4g/UctimvNqjDbj1RBnXMM4ixy3UdlxEDAAAAAAC0SD6PIP/kk0/0/PPPq3///l7Lp02bpnfeeUeLFi1ScXGx9u7dq7FjxzY6UQAAAAAAAAAA/MmnDvKDBw/quuuu04svvqi2bdt6lldUVGjevHl68sknNWzYMGVnZ2v+/Pn6+OOPtXbtWr8lDQAAAAAAAABAY/nUQT5p0iRdfPHFGjFihNfykpISHTt2zGt5r169lJmZqTVr1tRbl9vtVmVlpdcDAAAAAAAAAIBAczwH+cKFC/Xpp5/qk08+OaGstLRU0dHRSkpK8lqekpKi0tLSeusrKCjQjBkznKYBAAAAAAAAAECjOOog37Nnj+644w4tX75cMTExfkkgPz9f06dP9/xfWVmpjAy7WysCAAAAANDyFRYWqrCwULt27ZIk9enTRw888IBGjRolSRo6dKiKi4u9nnPrrbfqueeea+pUgROUlGw1ihuYnWUUF2G43lrDuBrDuCjDOMswzt9M1/udn9drejz8XZ+/97Pp9Bqm6zXdjk2Grw8EnqMO8pKSEu3fv18DBw70LKupqdHq1av1hz/8QcuWLVN1dbXKy8u9RpGXlZUpNTW13jpdLpdcLpdv2QMAAAAA0IJ16tRJs2bNUo8ePWRZll566SWNGTNGGzZsUJ8+fSRJt9xyi2bOnOl5TlxcXLDSBQCg2XHUQT58+HBt3rzZa9nNN9+sXr166Z577lFGRoZat26tlStXKi8vT5K0bds27d69Wzk5Of7LGgAAAACAMDB69Giv/x955BEVFhZq7dq1ng7yuLi4BgelAQCAk3PUQR4fH6++fft6LTvllFPUrl07z/IJEyZo+vTpSk5OVkJCgqZMmaKcnBwNHjzYf1kDAAAAABBmampqtGjRIh06dMhrENorr7yil19+WampqRo9erTuv/9+RpEDAGDI8U067Tz11FOKjIxUXl6e3G63cnNzNXfuXH+vBj4wmVur3A/1JBnUUeWHPKINYg7aRvSxjajRlkbnYvJCO2xT3s+gjjKDmHibbf7GoI69BjHVBjF2VjAfFwA/2OrwvSTLcA7MOqZzXNaJNGh7fizCoC1qDNN5F+s43WZfOM0p0Jpim52fS4EVasdAMp8Dto4vrzenx8HpPKhO63ca7/Q9D83D5s2blZOTo6NHj6pNmzZasmSJsrK+b6+uvfZade7cWenp6dq0aZPuuecebdu2TYsXL26wPrfbLbfb7fm/srIy4NsAAECoanQH+apVq7z+j4mJ0Zw5czRnzpzGVg0AAAAAQNjr2bOnNm7cqIqKCr3xxhsaP368iouLlZWVpYkTJ3ri+vXrp7S0NA0fPlw7duxQt27d6q2voKBAM2bMaKr0AQAIaaE4MAQAAAAAAPz/oqOj1b17d2VnZ6ugoEADBgzQ008/XW/soEGDJEnbt29vsL78/HxVVFR4Hnv27AlI3gAANAd+n2IFAAAAAAAETm1trdcUKcfbuHGjJCktLa3B57tcLrlcrkCkBgBAs0MHOQAAAAAAISo/P1+jRo1SZmamqqqqVFRUpFWrVmnZsmXasWOHioqKdNFFF6ldu3batGmTpk2bpiFDhqh///7BTh0AgGaBDnIAAAAAAELU/v37deONN2rfvn1KTExU//79tWzZMl1wwQXas2ePVqxYodmzZ+vQoUPKyMhQXl6e7rvvvmCnDQBAs0EHOQAAAAAAIWrevHkNlmVkZKi4uLgJswEC49OSrUZxP8nOMorz9w33agzjLMO4CF8TaWR9pvn5e72mx8N0PwdLbbATQMBwk04AAAAAAAAAQFhiBHkLkmvzS6rJfcnj/RDTzaCOHQYx/vCdTXmcUS19bCOqbMpNXmjRNuUmx6/cICbJptzkVj3tDWL+aDgCAAAAAAAAAAgWRpADAAAAAAAAAMISHeQAAAAAAAAAgLBEBzkAAAAAAAAAICzRQQ4AAAAAAAAACEvcpBMAAOA4Wx3eZDjL5ibZ/mF/w+jjRWlLgPL4XiiOsKgNcP2+bLPTnAK9X5viuDldR0SA669xGC85P25O452+xwAAACCwQvH7DQAA8LPCwkL1799fCQkJSkhIUE5Ojt5//31P+dGjRzVp0iS1a9dObdq0UV5ensrKyoKYMQAAAAAAgUcHOQAAYaBTp06aNWuWSkpKtH79eg0bNkxjxozRli3fjzSeNm2a3nnnHS1atEjFxcXau3evxo4dG+SsAQAAAAAILKZYAQAgDIwePdrr/0ceeUSFhYVau3atOnXqpHnz5qmoqEjDhg2TJM2fP1+9e/fW2rVrNXjw4GCkDAAAAHjZYDhN1U/8PAWe6ehSX6b2akpWkOrz93qDZQvTpLVYdJC3IIebaD1pNuXtDeo406bc5KL+zQYxQ2zK1xvUUWUQE+eHOqJsyg8Y1LGMN2sABmpqarRo0SIdOnRIOTk5Kikp0bFjxzRixAhPTK9evZSZmak1a9bQQQ4AAAAAaLHoIAcAIExs3rxZOTk5Onr0qNq0aaMlS5YoKytLGzduVHR0tJKSkrziU1JSVFpa2mB9brdbbrfb839lZWWgUgcAAAAAICCYgxwAgDDRs2dPbdy4UevWrdPtt9+u8ePHa+tW3688KSgoUGJioueRkZHhx2wBAAAAAAg8OsgBAAgT0dHR6t69u7Kzs1VQUKABAwbo6aefVmpqqqqrq1VeXu4VX1ZWptTU1Abry8/PV0VFheexZ8+eAG8BAAAAAAD+RQc5AABhqra2Vm63W9nZ2WrdurVWrlzpKdu2bZt2796tnJycBp/vcrmUkJDg9QAAAAAAoDlhDnIAAMJAfn6+Ro0apczMTFVVVamoqEirVq3SsmXLlJiYqAkTJmj69OlKTk5WQkKCpkyZopycHG7QCQAAAABo0eggBwAgDOzfv1833nij9u3bp8TERPXv31/Lli3TBRdcIEl66qmnFBkZqby8PLndbuXm5mru3LlBzhoAAAAAgMCigxwAgDAwb968k5bHxMRozpw5mjNnThNlBAAAAABA8NFBDgAA0AhbS7Y6is/KzgpQJj+IVB9H8RHa4jA+9EQFO4F6hNrNfpriuFmOn+HsXK1xWHutw3hfOH0PAAAAQGihg7yZyDX4Ml1tU97OYD3pBjHtbcrLDerI8EMeJl9F1tuUm3zJOmwQE2dTbvKl/Q2+XAEAAAAA0GgbDL9f9zccuGD6g2uwBhE4/4H65Pz9A7O/Bw2Y5me63qb4QR2hLdQGtgAAAAAAAAAA0CToIAcAAAAAAAAAhCU6yAEAAAAAAAAAYYkOcgAAAAAAAABAWKKDHAAAAAAAAAAQluggBwAAAAAAAACEJTrIAQAAAAAAAABhqVWwE4B0eXaWbUy5QT2n25Tbr0Xa44eYQQZ1lNuUbzCoY59BjJ1vDGKqDWKKSrY2NhUAAAAAAAAATYwR5AAAAAAAAACAsMQIcgAAAAAAAKABlmFcRJDWa8o0P3+Ppq31c33+Xu8WZgUIe3SQAwAAtDBOv4REqo/DZ2xxGO+cv79gNpYvXxSdHgenX4Kd7iOn9fv2ZdbpueSM05y28oUXAAAANphiBQAAAAAAAAAQluggBwAAAAAAAACEJTrIAQAAAAAAAABhiQ5yAAAAAAAAAEBYooMcAAAAAAAAABCWWgU7AUixBjHfGcRk2ZRHGdRx2CCmp02526COb2zKPzSoY5dBTJxN+fySrQa1AAAAAAAAAGiJGEEOAAAAAAAAAAhLdJADAAAAAAAAAMISU6wAAAAAANAMzJo1S/n5+brjjjs0e/ZsSdLRo0d15513auHChXK73crNzdXcuXOVkpIS3GSBZmCT4bSr/bPtJrX9ntWYZJqgPlO1hnGhPup2C9PqwlCon8sAAAAAAIS9Tz75RM8//7z69+/vtXzatGl65513tGjRIhUXF2vv3r0aO3ZskLIEAKD5oYMcAAAAAIAQdvDgQV133XV68cUX1bZtW8/yiooKzZs3T08++aSGDRum7OxszZ8/Xx9//LHWrl0bxIwBAGg+mGIFAACgCW314VLPLMPLen1lehntD/o4ivZlRIalLT48K3Bqgp1APZzn5Oy4OT8vnPPl9QCEo0mTJuniiy/WiBEj9PDDD3uWl5SU6NixYxoxYoRnWa9evZSZmak1a9Zo8ODBwUgXAIBmhQ5yAAAAAABC1MKFC/Xpp5/qk08+OaGstLRU0dHRSkpK8lqekpKi0tLSBut0u91yu92e/ysrK/2WLwAAzQ0d5E3gCptRXyYHIc4gxm78jckooxH2IbrapvxjgzqqbcqjDOow2Z75jEoCAAAA0Ezt2bNHd9xxh5YvX66YmBi/1VtQUKAZM2b4rT4AAJoz5iAHAAAAACAElZSUaP/+/Ro4cKBatWqlVq1aqbi4WM8884xatWqllJQUVVdXq7y83Ot5ZWVlSk1NbbDe/Px8VVRUeB579uwJ8JYAABC6GEEOAAAAAEAIGj58uDZv3uy17Oabb1avXr10zz33KCMjQ61bt9bKlSuVl5cnSdq2bZt2796tnJycBut1uVxyuVwBzR0AgOaCDnIAAAAAAEJQfHy8+vbt67XslFNOUbt27TzLJ0yYoOnTpys5OVkJCQmaMmWKcnJyuEEnAACG6CAHAAAAAKCZeuqppxQZGam8vDy53W7l5uZq7ty5wU4LAIBmgw5yAAAAAACaiVWrVnn9HxMTozlz5mjOnDnBSQgIAxF+rs/yc321fq7P3+v19/ZuLdnq5xoR7rhJJwAAAAAAAAAgLNFBDgAAAAAAAAAIS3SQAwAAAAAAAADCEnOQN9KI7CzbmDI/rCfFIOaITXlHgzpMcl1iU77eoI4DNuXPM58UAAAAAAAAgABjBDkAAAAAAAAAICwxghwAACDEbXV4ZVWWwRVuTcmXERm16uP3PFqaz7jiDgAAAGg0RpADAAAAAAAAAMISHeQAAAAAAAAAgLBEBzkAAAAAAAAAICwxBzkAAAAAAADQgH8Y3vdjgOF9YCzD9dYaxpnW529O75MDhCpGkAMAAAAAAAAAwhId5AAAAAAAAACAsMQUK43k9kMdNX6KibIpjzaoY6NBzGabcpNc53MZDgAAAAAAAIAgYwQ5AAAAAAAAACAs0UEOAAAAAAAAAAhLdJADAAAAAAAAAMISHeQAAAAAAAAAgLDETToBAABamK0Ob4adlZ0VoEy+911Aa28aTvcpAAAAgOaBEeQAAISBwsJC9e/fXwkJCUpISFBOTo7ef/99T/nQoUMVERHh9bjtttuCmDEAAAAAAIHHCHIAAMJAp06dNGvWLPXo0UOWZemll17SmDFjtGHDBvXp00eSdMstt2jmzJme58TFxQUrXQAAAKDZ8fdVc1u4gg1oEo46yB966CHNmDHDa1nPnj31+eefS5KOHj2qO++8UwsXLpTb7VZubq7mzp2rlJQU/2UcYj4MoTery20uj47y03r+N4S2GQBgZvTo0V7/P/LIIyosLNTatWs9HeRxcXFKTU0NRnoAAAAAAASF4ylW+vTpo3379nkeH330kads2rRpeuedd7Ro0SIVFxdr7969Gjt2rF8TBgAAjVNTU6OFCxfq0KFDysnJ8Sx/5ZVX1L59e/Xt21f5+fk6fPhwELMEAAAAACDwHE+x0qpVq3pHl1VUVGjevHkqKirSsGHDJEnz589X7969tXbtWg0ePLjx2QIAAJ9t3rxZOTk5Onr0qNq0aaMlS5YoK+v7q4+uvfZade7cWenp6dq0aZPuuecebdu2TYsXL26wPrfbLbfb7fm/srIy4NsAAAAAAIA/Oe4g/+KLL5Senq6YmBjl5OSooKBAmZmZKikp0bFjxzRixAhPbK9evZSZmak1a9Y02EHOl2sAAJpGz549tXHjRlVUVOiNN97Q+PHjVVxcrKysLE2cONET169fP6WlpWn48OHasWOHunXrVm99BQUFJ0y9BgAAAABAc+JoipVBgwZpwYIFWrp0qQoLC7Vz506dd955qqqqUmlpqaKjo5WUlOT1nJSUFJWWljZYZ0FBgRITEz2PjIwMnzYEAACcXHR0tLp3767s7GwVFBRowIABevrpp+uNHTRokCRp+/btDdaXn5+viooKz2PPnj0ByRsAAAAAgEBxNIJ81KhRnr/79++vQYMGqXPnznr99dcVGxvrUwL5+fmaPn265//Kyko6yQEAaAK1tbVeV3Edb+PGjZKktLS0Bp/vcrnkcrkCkRoAAAAAAE3C8RQrx0tKStLpp5+u7du364ILLlB1dbXKy8u9RpGXlZXVO2d5Hb5cAwAQePn5+Ro1apQyMzNVVVWloqIirVq1SsuWLdOOHTtUVFSkiy66SO3atdOmTZs0bdo0DRkyRP379w926gAAAAAABIyjKVZ+7ODBg9qxY4fS0tKUnZ2t1q1ba+XKlZ7ybdu2affu3crJyWl0ogAAwHf79+/XjTfeqJ49e2r48OH65JNPtGzZMl1wwQWKjo7WihUrdOGFF6pXr1668847lZeXp3feeSfYaQMAAAAAEFCORpDfddddGj16tDp37qy9e/fqwQcfVFRUlK655holJiZqwoQJmj59upKTk5WQkKApU6YoJyenwRt01seyLMcbge8dq6k5aXm1QR0nrwEA0FjBaufmzZvXYFlGRoaKi4sbvQ7a8OarxuYzBACgZbdzLXnbgKbEZyog9Ji0cY46yL/88ktdc801OnDggDp06KBzzz1Xa9euVYcOHSRJTz31lCIjI5WXlye3263c3FzNnTvXUdJVVVWO4vGDdzduC3YKAAAbVVVVSkxMDHYaAUEb3nxt4zMEANiiDQdg53M+UwEhx6T9jrBC7Kfi2tpa7d27V/Hx8YqIiJD0w4079+zZo4SEhCBn2HKwXwOD/RoY7NfAYL8Gzo/3rWVZqqqqUnp6uiIjGzXDWciqrw2XwvM8Y5vZ5paKbQ6PbZbCc7sb2uZwbcNbyjnAdoQWtiO0sB2hhe3wLyftd6Nu0hkIkZGR6tSpU71lCQkJzfoECVXs18BgvwYG+zUw2K+Bc/y+bamjzuqcrA2XwvM8Y5vDA9scHsJxm6Xw3O76tjmc2/CWcg6wHaGF7QgtbEdoYTv8x7T9bpk/fwMAAAAAAAAAYIMOcgAAAAAAAABAWGoWHeQul0sPPvigXC5XsFNpUdivgcF+DQz2a2CwXwOHffuDcNwXbHN4YJvDQzhusxSe2x2O23wyLWV/sB2hhe0ILWxHaGE7gifkbtIJAAAAAAAAAEBTaBYjyAEAAAAAAAAA8Dc6yAEAAAAAAAAAYYkOcgAAAAAAAABAWKKDHAAAAAAAAAAQlkK+g3zOnDnq0qWLYmJiNGjQIP39738PdkrNzurVqzV69Gilp6crIiJCb731lle5ZVl64IEHlJaWptjYWI0YMUJffPFFcJJtJgoKCnTWWWcpPj5eHTt21GWXXaZt27Z5xRw9elSTJk1Su3bt1KZNG+Xl5amsrCxIGTcfhYWF6t+/vxISEpSQkKCcnBy9//77nnL2a+PNmjVLERERmjp1qmcZ+9U3Dz30kCIiIrwevXr18pSzX78XTm253TnREoTj5wq7bb7ppptOOO4jR44MTrJ+Eo6fdUy2eejQoScc69tuuy1IGTdeOH7ustvmlnaMfdUS2u7m2ia3lHa2JbSdLaUtbCntW0tps1piO9QS+hhCuoP8tdde0/Tp0/Xggw/q008/1YABA5Sbm6v9+/cHO7Vm5dChQxowYIDmzJlTb/ljjz2mZ555Rs8995zWrVunU045Rbm5uTp69GgTZ9p8FBcXa9KkSVq7dq2WL1+uY8eO6cILL9ShQ4c8MdOmTdM777yjRYsWqbi4WHv37tXYsWODmHXz0KlTJ82aNUslJSVav369hg0bpjFjxmjLli2S2K+N9cknn+j5559X//79vZazX33Xp08f7du3z/P46KOPPGXs1/Bsy092TrQE4fi5wm6bJWnkyJFex/3VV19twgz9Lxw/65hssyTdcsstXsf6scceC1LGjReOn7vstllqWcfYFy2p7W6ObXJLaWdbQtvZUtrCltK+tZQ2q6W1Qy2mj8EKYWeffbY1adIkz/81NTVWenq6VVBQEMSsmjdJ1pIlSzz/19bWWqmpqdbjjz/uWVZeXm65XC7r1VdfDUKGzdP+/fstSVZxcbFlWd/vw9atW1uLFi3yxPzzn/+0JFlr1qwJVprNVtu2ba3//d//Zb82UlVVldWjRw9r+fLl1s9+9jPrjjvusCyL87UxHnzwQWvAgAH1lrFfvxdubfnJzomWKBw/V/x4my3LssaPH2+NGTMmKPk0lXD8rPPjbbYsy6v9bKnC8XNX3TZbVngcYzstpe1uCW1yS2lnW0rb2VLawpbUvrWUNqu5tkMtqY8hZEeQV1dXq6SkRCNGjPAsi4yM1IgRI7RmzZogZtay7Ny5U6WlpV77OTExUYMGDWI/O1BRUSFJSk5OliSVlJTo2LFjXvu1V69eyszMZL86UFNTo4ULF+rQoUPKyclhvzbSpEmTdPHFF3vtP4nztbG++OILpaen67TTTtN1112n3bt3S2K/SuHbljd0ToSDcP5csWrVKnXs2FE9e/bU7bffrgMHDgQ7Jb8Kx886P97mOq+88orat2+vvn37Kj8/X4cPHw5Gen4Xjp+7frzNdVrqMTbR0trultYmt7R2trm1nS2lLWwJ7VtLabOaezvUkvoYWgU7gYZ88803qqmpUUpKitfylJQUff7550HKquUpLS2VpHr3c10ZTq62tlZTp07VOeeco759+0r6fr9GR0crKSnJK5b9ambz5s3KycnR0aNH1aZNGy1ZskRZWVnauHEj+9VHCxcu1KeffqpPPvnkhDLOV98NGjRICxYsUM+ePbVv3z7NmDFD5513nj777DP2q8KzLT/ZOREfHx/s9AIuXD9XjBw5UmPHjlXXrl21Y8cO3XvvvRo1apTWrFmjqKioYKfXaOH4Wae+bZaka6+9Vp07d1Z6ero2bdqke+65R9u2bdPixYuDmG3jhOPnroa2WWqZx9iJltR2t8Q2uSW1s82t7WwpbWFzb99aSpvVEtqhltbHELId5EBzMWnSJH322WfNYj675qJnz57auHGjKioq9MYbb2j8+PEqLi4OdlrN1p49e3THHXdo+fLliomJCXY6LcqoUaM8f/fv31+DBg1S586d9frrrys2NjaImSFYTnZOTJgwIYiZIZDGjRvn+btfv37q37+/unXrplWrVmn48OFBzMw/wvGzTkPbPHHiRM/f/fr1U1pamoYPH64dO3aoW7duTZ2mX4Tj566GtjkrK6tFHuNwRZsc2ppb29lS2sLm3r61lDarubdDLbGPIWSnWGnfvr2ioqJOuMNpWVmZUlNTg5RVy1O3L9nPvpk8ebLeffddffDBB+rUqZNneWpqqqqrq1VeXu4Vz341Ex0dre7duys7O1sFBQUaMGCAnn76afarj0pKSrR//34NHDhQrVq1UqtWrVRcXKxnnnlGrVq1UkpKCvvVT5KSknT66adr+/btnK+iLZe8z4lwwOeK75122mlq3759izju4fhZp6Ftrs+gQYMkqVkf63D83NXQNtenJRxjJ1py290S2uSW3M6GctvZUtrCltC+tZQ2q7m3Qy2xjyFkO8ijo6OVnZ2tlStXepbV1tZq5cqVXvPyoHG6du2q1NRUr/1cWVmpdevWsZ9PwrIsTZ48WUuWLNH//d//qWvXrl7l2dnZat26tdd+3bZtm3bv3s1+9UFtba3cbjf71UfDhw/X5s2btXHjRs/jzDPP1HXXXef5m/3qHwcPHtSOHTuUlpbG+Sracsn7nAgHfK743pdffqkDBw406+Mejp917La5Phs3bpSkZn2sfywcP3fVbXN9WuIxPpmW3Ha3hDa5Jbezodh2tpS2sCW3by2lzWpu7VCL7GMI6i1CbSxcuNByuVzWggULrK1bt1oTJ060kpKSrNLS0mCn1qxUVVVZGzZssDZs2GBJsp588klrw4YN1n/+8x/Lsixr1qxZVlJSkvX2229bmzZtssaMGWN17drVOnLkSJAzD1233367lZiYaK1atcrat2+f53H48GFPzG233WZlZmZa//d//2etX7/eysnJsXJycoKYdfPwm9/8xiouLrZ27txpbdq0yfrNb35jRUREWH/9618ty2K/+suP74zNfvXNnXfeaa1atcrauXOn9be//c0aMWKE1b59e2v//v2WZbFfLSv82nK7c6IlCMfPFSfb5qqqKuuuu+6y1qxZY+3cudNasWKFNXDgQKtHjx7W0aNHg526z8Lxs47dNm/fvt2aOXOmtX79emvnzp3W22+/bZ122mnWkCFDgpy578Lxc9fJtrklHmNftJS2u7m2yS2lnW0JbWdLaQtbSvvWUtqsltoONfc+hpDuILcsy3r22WetzMxMKzo62jr77LOttWvXBjulZueDDz6wJJ3wGD9+vGVZllVbW2vdf//9VkpKiuVyuazhw4db27ZtC27SIa6+/SnJmj9/vifmyJEj1i9/+Uurbdu2VlxcnHX55Zdb+/btC17SzcTPf/5zq3PnzlZ0dLTVoUMHa/jw4Z4Gz7LYr/7y48aL/eqbq6++2kpLS7Oio6OtU0891br66qut7du3e8rZr98Lp7bc7pxoCcLxc8XJtvnw4cPWhRdeaHXo0MFq3bq11blzZ+uWW25pdh1JPxaOn3Xstnn37t3WkCFDrOTkZMvlclndu3e3fv3rX1sVFRXBTbwRwvFz18m2uSUeY1+1hLa7ubbJLaWdbQltZ0tpC1tK+9ZS2qyW2g419z6GCMuyLP+MRQcAAAAAAAAAoPkI2TnIAQAAAAAAAAAIJDrIAQAAAAAAAABhiQ5yAAAAAAAAAEBYooMcAAAAAAAAABCW6CAHAAAAAAAAAIQlOsgBAAAAAAAAAGGJDnIAAAAAAAAAQFiigxwAAAAAAAAAEJboIAfgZejQoRo6dGhQ1r1r1y5FRERowYIFnmUPPfSQIiIigpIPAAChivYaAIDQRlsNNB90kAMNWLBggSIiIhQREaGPPvrohHLLspSRkaGIiAhdcsklQcgw+GpqajR//nwNHTpUycnJcrlc6tKli26++WatX7++yfP56quvdNVVVykpKUkJCQkaM2aM/v3vfzd5HgCApkN7bS/U2uuFCxdq4MCBiomJUYcOHTRhwgR98803J8SVlZXp5ptvVseOHRUbG6uBAwdq0aJFTZ4vAKBxaKvthVJbvWTJEuXm5io9PV0ul0udOnXSFVdcoc8+++yE2Ndee03XX3+9evTooYiIiJP+IOB2u3XPPfcoPT1dsbGxGjRokJYvXx7ALQHM0UEO2IiJiVFRUdEJy4uLi/Xll1/K5XIFIavgO3LkiC655BL9/Oc/l2VZuvfee1VYWKgbb7xRa9as0dlnn60vv/yyyfI5ePCgzj//fBUXF+vee+/VjBkztGHDBv3sZz/TgQMHmiwPAEBw0F7XL9Ta68LCQl1zzTVKTk7Wk08+qVtuuUULFy7U8OHDdfToUU9cZWWlzj33XL355pu69dZb9cQTTyg+Pl5XXXVVvccZABD6aKvrF2pt9ebNm9W2bVvdcccdmjt3rm6//XZt2LBBZ599tv7xj394xRYWFurtt99WRkaG2rZte9J6b7rpJj355JO67rrr9PTTTysqKkoXXXRRvT+aAE2tVbATAELdRRddpEWLFumZZ55Rq1Y/vGSKioqUnZ1d74incPDrX/9aS5cu1VNPPaWpU6d6lT344IN66qmnmjSfuXPn6osvvtDf//53nXXWWZKkUaNGqW/fvvr973+vRx99tEnzAQA0Ldrr+oVSe11dXa17771XQ4YM0fLlyz2Xef/0pz/V6NGj9eKLL2rKlCmSpOeff17bt2/XypUrNWzYMEnS7bffrsGDB+vOO+/UFVdcoejo6CbLHQDQeLTV9QultlqSHnjggROW/eIXv1CnTp1UWFio5557zrP8T3/6k0499VRFRkaqb9++Ddb597//XQsXLtTjjz+uu+66S5J04403qm/fvrr77rv18ccf+39DAAcYQQ7YuOaaa3TgwAGvS3+qq6v1xhtv6Nprr633OU888YR++tOfql27doqNjVV2drbeeOONE+IiIiI0efJkvfLKK+rZs6diYmKUnZ2t1atXe8XVzRX2+eef66qrrlJCQoLatWunO+64w2u0VZ2XX35Z2dnZio2NVXJyssaNG6c9e/acEPfCCy+oW7duio2N1dlnn60PP/zQaJ98+eWXev7553XBBRec0IBLUlRUlO666y516tTJs+yrr77Sz3/+c6WkpMjlcqlPnz76f//v/xmtz8Qbb7yhs846y9M5Lkm9evXS8OHD9frrr/ttPQCA0ER7faJQa68/++wzlZeX6+qrr/aaA/WSSy5RmzZttHDhQs+yDz/8UB06dPB0jktSZGSkrrrqKpWWlqq4uNgvOQEAmg5t9YlCra1uSMeOHRUXF6fy8nKv5RkZGYqMtO9afOONNxQVFaWJEyd6lsXExGjChAlas2ZNvfsUaEp0kAM2unTpopycHL366queZe+//74qKio0bty4ep/z9NNP6yc/+YlmzpypRx99VK1atdKVV16p995774TY4uJiTZ06Vddff71mzpypAwcOaOTIkfXO73XVVVfp6NGjKigo0EUXXaRnnnnGq4GRpEceeUQ33nijevTooSeffFJTp07VypUrNWTIEK/GbN68ebr11luVmpqqxx57TOecc44uvfRSo4bp/fff13fffacbbrjBNlb6fg7RwYMHa8WKFZo8ebKefvppde/eXRMmTNDs2bON6jiZ2tpabdq0SWeeeeYJZWeffbZ27NihqqqqRq8HABC6aK9PFGrttdvtliTFxsaeUBYbG6sNGzaotrbWE1tfXFxcnCSppKSk0fkAAJoWbfWJQq2tPl55ebm+/vprbd68Wb/4xS9UWVmp4cOH+1TXhg0bdPrppyshIcFr+dlnny1J2rhxY2PTBRrHAlCv+fPnW5KsTz75xPrDH/5gxcfHW4cPH7Ysy7KuvPJK6/zzz7csy7I6d+5sXXzxxV7PrYurU11dbfXt29caNmyY13JJliRr/fr1nmX/+c9/rJiYGOvyyy/3LHvwwQctSdall17q9fxf/vKXliTrH//4h2VZlrVr1y4rKirKeuSRR7ziNm/ebLVq1cqzvLq62urYsaN1xhlnWG632xP3wgsvWJKsn/3sZyfdN9OmTbMkWRs2bDhpXJ0JEyZYaWlp1jfffOO1fNy4cVZiYqJnf+3cudOSZM2fP/+EbT+Zr7/+2pJkzZw584SyOXPmWJKszz//3ChXAEDzQnvdsFBsryMiIqwJEyZ4Lf/88889+7hu3VOmTLEiIyOtXbt2nZCLJGvy5MlG2wQACD7a6oaFWlt9vJ49e3r2a5s2baz77rvPqqmpaTC+T58+DW5vnz59TjhmlmVZW7ZssSRZzz33nHFeQCAwghwwcNVVV+nIkSN69913VVVVpXfffbfBS8Ak75FR//3vf1VRUaHzzjtPn3766QmxOTk5ys7O9vyfmZmpMWPGaNmyZaqpqfGKnTRpktf/dfN0/uUvf5EkLV68WLW1tbrqqqv0zTffeB6pqanq0aOHPvjgA0nS+vXrtX//ft12221e83fedNNNSkxMtN0flZWVkqT4+HjbWMuy9Oabb2r06NGyLMsrr9zcXFVUVNS7X5w4cuSIJNV7U5eYmBivGABAy0V77S3U2uv27dvrqquu0ksvvaTf//73+ve//60PP/xQV199tVq3bi3ph/b6F7/4haKionTVVVfp448/1o4dO1RQUKAlS5Z4xQEAmhfaam+h1lYfb/78+Vq6dKnmzp2r3r1768iRIyfsR1NHjhzh+zpCGjfpBAx06NBBI0aMUFFRkQ4fPqyamhpdccUVDca/++67evjhh7Vx40bP5cSSvObbrNOjR48Tlp1++uk6fPiwvv76a6WmpjYY261bN0VGRmrXrl2SpC+++EKWZdVbpyTPl8///Oc/9dbXunVrnXbaaQ1uV526y6JMpi35+uuvVV5erhdeeEEvvPBCvTH79++3redk6j40Hb+v69TNI1ffZdoAgJaF9tpbqLXX0vc33zxy5Ijuuusuz026rr/+enXr1k2LFy9WmzZtJEn9+/dXUVGRbrvtNp1zzjmSpNTUVM2ePVu33367Jw4A0LzQVnsLxba6Tk5OjufvcePGqXfv3pK+nxfeqdjYWL6vI6TRQQ4Yuvbaa3XLLbeotLRUo0aNUlJSUr1xH374oS699FINGTJEc+fOVVpamlq3bq358+erqKjIrzn9+ENBbW2tIiIi9P777ysqKuqEeH99mezVq5ckafPmzTrjjDNOGls3l+j111+v8ePH1xvTv3//RuWTnJwsl8ulffv2nVBWtyw9Pb1R6wAANA+01z8ItfZakhITE/X2229r9+7d2rVrlzp37qzOnTvrpz/9qTp06OB1vK644gpdeuml+sc//qGamhoNHDhQq1atkvR9hwcAoHmirf5BKLbV9Wnbtq2GDRumV155xacO8rS0NH311VcnLOf7OkIFHeSAocsvv1y33nqr1q5dq9dee63BuDfffFMxMTFatmyZ1yVE8+fPrzf+iy++OGHZv/71L8XFxalDhw4nxHbt2tXz//bt21VbW6suXbpI+v5Xb8uy1LVr15N+cezcubOnvmHDhnmWHzt2TDt37tSAAQMafK4kjRo1SlFRUXr55ZdtbybSoUMHxcfHq6amRiNGjDhprK8iIyPVr18/rV+//oSydevW6bTTTjO6ZA0A0PzRXv8g1Nrr42VmZiozM1PS9zcBKykpUV5e3glx0dHROuusszz/r1ixQpKaJEcAQGDQVv8glNvqHzty5IgqKip8eu4ZZ5yhDz74QJWVlV436ly3bp2nHAgm5iAHDLVp00aFhYV66KGHNHr06AbjoqKiFBER4TU3165du/TWW2/VG79mzRqvecL27Nmjt99+WxdeeOEJv1TPmTPH6/9nn31W0veNqiSNHTtWUVFRmjFjhizL8oq1LEsHDhyQJJ155pnq0KGDnnvuOVVXV3tiFixY4HU37oZkZGTolltu0V//+ldPDserra3V73//e3355ZeKiopSXl6e3nzzzXrvHv7111/brs/EFVdcoU8++cSrk3zbtm36v//7P1155ZV+WQcAIPTRXv8gFNvr+uTn5+u7777TtGnTThr3xRdf6LnnntMll1zCCHIAaMZoq38Qim11fdO07Nq1SytXrtSZZ57pU51XXHGFampqvKaGcbvdmj9/vgYNGqSMjAyf8wX8gRHkgAMNXcZ0vIsvvlhPPvmkRo4cqWuvvVb79+/XnDlz1L17d23atOmE+L59+yo3N1e/+tWv5HK5NHfuXEnSjBkzTojduXOnLr30Uo0cOVJr1qzRyy+/rGuvvdbzq3S3bt308MMPKz8/X7t27dJll12m+Ph47dy5U0uWLNHEiRN11113qXXr1nr44Yd16623atiwYbr66qu1c+dOzZ8/32ieNEn6/e9/rx07duhXv/qVFi9erEsuuURt27bV7t27tWjRIn3++ecaN26cJGnWrFn64IMPNGjQIN1yyy3KysrSt99+q08//VQrVqzQt99+a7TOk/nlL3+pF198URdffLFnG5/8/9r792i76vJe/H82uWwISXYIl1xKoAgIBgjWKDFV0QoVqMfhBTu02l/R40+/eoJflXpq02GLejwNx55hrT2IPUcO6Lcira1o9VuhFiW0FShEOVyiKdBYYiFBqbkQyM5lz98f/RmNBPI8yZpZe+35eo2xxyA7b575mXOuNZ+5nr2y9kc/GnPmzInf/M3fPOD6AAwO/fonxlu/vuyyy+Kee+6JJUuWxOTJk+OLX/xi/M3f/E18+MMf3uOd4hERCxcujF/91V+N4447LtauXRtXXHFFzJ49Oz75yU8e8DoA6C+9+ifGW68+44wz4pxzzolnP/vZccQRR8R9990XV155ZezYsSMuu+yyPbI333xz3HzzzRHx7wP6rVu3xoc//OGIiDj77LPj7LPPjoiIJUuWxK/+6q/G8uXL45FHHomTTjopPv3pT8f3vve9uPLKKw94zXDAGmCvrrrqqiYimttvv/1pc8cff3zz8pe/fI/vXXnllc3JJ5/cDA8PN6eeempz1VVXNZdeemnzs0+5iGiWLVvW/Omf/unu/C/8wi803/jGN/bI/fj/Xb16dfPa1762mTFjRnPEEUc0F198cfPEE088aU1/+Zd/2bzwhS9sDj/88Obwww9vTj311GbZsmXNmjVr9sh94hOfaE444YRmeHi4ee5zn9vcfPPNzYtf/OLmxS9+ceoY7dy5s/nUpz7VvOhFL2pGRkaaKVOmNMcff3zz5je/ufn2t7+9R3bDhg3NsmXLmgULFjRTpkxp5s6d25xzzjnN//yf/3N3Zu3atU1ENFddddWT9j1j3bp1zWtf+9pm5syZzfTp05v/8B/+Q3Pfffel/l8ABpN+vW/jqV9/5Stfac4666xmxowZzbRp05rnP//5zZ//+Z/vNfv617++WbBgQTN16tRm/vz5zdvf/vZmw4YNqX0GYPzQq/dtPPXqSy+9tHnuc5/bHHHEEc3kyZOb+fPnN69//eubu+66a6/ZiNjr16WXXrpH9oknnmje+973NnPnzm2Gh4eb5z3vec3111+fOj7QtqGm+Zl/KwIcNENDQ7Fs2bL4H//jfzxt7gMf+EB88IMfjB/84Adx1FFHHaTVAQAR+jUAjHd6NXAgfAY5AAAAAACdZEAOAAAAAEAnGZADAAAAANBJPoMcAAAAAIBO8g5yAAAAAAA6yYAcAAAAAIBOMiAHAAAAAKCTJvd7AT9rbGwsHnrooZgxY0YMDQ31ezkA0BNN08SWLVti/vz5ccghE/Pn03o4ABORHg4Ag6fSv8fdgPyhhx6KBQsW9HsZANCKdevWxbHHHtvvZbRCDwdgItPDAWDwZPp3awPyyy+/PP7gD/4g1q9fH2eeeWb88R//cZx11ln7/P9mzJjR1pIA9suSZ5+Szh6WzD1e2P6UZG64UHNWMldZ5/V3rimku2sQ+pweDkwUpxZ6eFbT84q1z70ca2H7a/TwlPHe5/a3f0eM/30DuqfSw7N9tI0e2sa/K6qs87t6+D5lelwrA/I/+7M/i0suuSQ++clPxpIlS+JjH/tYnHfeebFmzZo45phjnvb/9c+5gPFm8qRJ+WyPc23VzA7dsznyxnuf08OBiWRSoYdn9XtA7krbP+O5zx1I/44Y3/sGdFOlh2f7aBtXujYG5K7IvZXpca18gNpHP/rReOtb3xpvfvObY+HChfHJT34ypk2bFv/7f//vNjYHAPSIHg4Ag0f/BoD91/MB+fbt22PVqlVx7rnn/mQjhxwS5557btxyyy1Pyo+OjsbmzZv3+AIADj49HAAGT7V/R+jhAPDTej4g/+EPfxi7du2KOXPm7PH9OXPmxPr165+UX7FiRYyMjOz+8otBAKA/9HAAGDzV/h2hhwPAT2vlI1Yqli9fHps2bdr9tW7dun4vCQBI0MMBYDDp4QDwEz3/JZ1HHXVUTJo0KTZs2LDH9zds2BBz5859Un54eDiGh4d7vQwAoEgPB4DBU+3fEXo4APy0nr+DfOrUqbF48eK48cYbd39vbGwsbrzxxli6dGmvNwcA9IgeDgCDR/8GgAPT83eQR0RccsklcdFFF8Vzn/vcOOuss+JjH/tYbN26Nd785je3sTlgQC1ZvDCdPSyZGypsf2chmzUjmZteqPlwMre1UHMsmZtUqPmC5Pn8h1WrC1U52PRwIGNhoYe3IdvvK/cFvd52RESTzGX7ckT+HU6VmtnzuVoPH7f0byCr0sOzPa/fn988KOtsw2nJ83mvHv60WhmQv+51r4sf/OAH8Xu/93uxfv36ePaznx3XX3/9k35pCAAwvujhADB49G8A2H9DTdNk39hwUGzevDlGRkb6vQzgIJiI7yA/KpmrXHiz7yDfVag5K5mrvIP8sWSu6+8g37RpU8ycObPfy2iFHg7d0eV3kFdk+33lvqCNd5Bndf0d5Ho4MBF0+R3k42oA+jTa6OFdfgd5pn/3+zEMAAAAAAB9YUAOAAAAAEAnGZADAAAAANBJBuQAAAAAAHSSATkAAAAAAJ1kQA4AAAAAQCcZkAMAAAAA0EkG5AAAAAAAdNLkfi8AmHjevHhhKve9Qs2RZO6RQs3hZG6oUHNTMrejUDNraiH7eDI3qVDz8GRuSfLxEZHfp79btTpdE4CntrBwjc7KviOn3+/caXqci6j10ayxFmpmnV54fGTXuVoPB+iJ05LX6Eq/baPnVF5fD4LK/mTvIdq4J8o+Pirbv3sC9fB+34cCAAAAAEBfGJADAAAAANBJBuQAAAAAAHSSATkAAAAAAJ1kQA4AAAAAQCcZkAMAAAAA0EkG5AAAAAAAdJIBOQAAAAAAnWRADgAAAABAJ03u9wKA/lqxeGEq94NCzSaZO6NQc2oy90Sh5uPJ3KGFmtl9HyrU7KddheymZK6y79uTuTcnH8cREVetWl1YAcD4tTB57WvjHTGVmm30vGy/rcius989PLv9yjka25+F9Gj72cdxRMRqPRyYIM4oXPv6KXstr/TGfvfRrOw6K/ck/dz3Nu4HK4/ju8d5D/cOcgAAAAAAOsmAHAAAAACATjIgBwAAAACgkwzIAQAAAADoJANyAAAAAAA6yYAcAAAAAIBOMiAHAAAAAKCTDMgBAAAAAOgkA3IAAAAAADrJgBwAAAAAgE6a3O8FAO34ncULU7nVyXq7CtueVchmbU/mjizUzO5TZd+nJHOHFWqOJnOVdU5N5oYLNSclc48WamZtKWR/Jfnc+OtV2WcHQG+dnrxOZa+7TWHbQ4VsVvYdOZU+1obsvleOZ6+3XTFWyGbPUaVm9vFZecfWwuz9rR4O9MkZyetU9trXRs9pQ6WPZftDNheRP06Ve402emP2OO1soWZFG4+7Rcnnxl196uHeQQ4AAAAAQCcZkAMAAAAA0EkG5AAAAAAAdJIBOQAAAAAAnWRADgAAAABAJxmQAwAAAADQSQbkAAAAAAB0kgE5AAAAAACdZEAOAAAAAEAnTe73AoCIixYvTOVmF2qemMydkMw9Utj2ccncPxdqrk7mxgo1R5O5WYWaTyRzuwo1pyVzWwo1syYVstl9OqZQM7tPmwo1DytkAfZlYbKHV96VMiWZq/S8fqr0vH7KrrNyLrN9tHKMhnqci8g/ltp48dgUst7dBfTSaS308Ox1P3vty94TROSv5ZX9qVyjs7L7VNl2tj9VemMbPWdnMld5HZ7V7/vGNh5LveQeAwAAAACATur5gPwDH/hADA0N7fF16qmn9nozAECP6eEAMJj0cADYf618xMppp50Wf/u3f/uTjUz2SS4AMAj0cAAYTHo4AOyfVjrm5MmTY+7cuW2UBgBapIcDwGDSwwFg/7TyGeT33XdfzJ8/P57xjGfEG9/4xnjwwQefMjs6OhqbN2/e4wsA6A89HAAGkx4OAPun5wPyJUuWxNVXXx3XX399XHHFFbF27dp40YteFFu2bNlrfsWKFTEyMrL7a8GCBb1eEgCQoIcDwGDSwwFg/w01TdO0uYGNGzfG8ccfHx/96EfjLW95y5P+fnR0NEZHR3f/efPmzZoznXPR4oWp3OxCzdOTubFk7pHCto9L5v65UHN1Mpfdn4iI9cncrELNJ5K5XYWaWXt/+bN3hyVz0wo1s/s0tVAzu0+HFmpm9/2vVmUfdTWbNm2KmTNntlK71/Rw2LeFyR5eeVfKlGQu2/MqN/tDhWxWqy82eih7PCvnclIyV7kvyJ6jSs3svrfx+ZyVe7ese/RwPRwSTmuhh2ev+9neWLnuttHH2ujh2fucyrazx31noWYbH7mR3X4bvbFSs43znq15dws9PNO/W/+tHbNmzYpnPvOZcf/99+/174eHh2N4eLjtZQAARXo4AAwmPRwA8lr5DPKf9thjj8UDDzwQ8+bNa3tTAEAP6eEAMJj0cADI6/mA/L3vfW+sXLkyvve978U3v/nNePWrXx2TJk2KX/u1X+v1pgCAHtLDAWAw6eEAsP96/hEr3//+9+PXfu3X4tFHH42jjz46XvjCF8att94aRx99dK83BePaG5KfZxYR8a5k7kuF7Wf/weRzk7mrC9vOfrb49ws15ydzDxdqzkjmKp+/Pr2QzRrddyQiap/tnVX5XPOs7GeAR+Q/n67yGeSzkrlfLDyHv9nSZ50ebHo4/Lvs54pH5N9tkv1szIj8Z0S2cd3fnsxV3mWTzVY+F7QNbfzT2jY+Q7SNzwXN7ntlf7I1K8c9+xipPIdX6+EwoWQ/Vzwif/2p9NtKv+/1trP7k319GZF/PZa9f4jIH6M2emjlg6Wy26/8zo/sOWqj11cem9l+28Y6zyg8h3v5eeU9H5Bfe+21vS4JABwEejgADCY9HAD2X+ufQQ4AAAAAAOORATkAAAAAAJ1kQA4AAAAAQCcZkAMAAAAA0EkG5AAAAAAAdJIBOQAAAAAAnWRADgAAAABAJxmQAwAAAADQSZP7vQAYDz68eGE6e1Iyd3hh+/+UzO0s1BxJ5r6XzB1b2HZ2fyo1R5O5yjHakcwtKNTcmsxtL9TMbn9doWbWpBayjxdqTk3mKsfziWRuuFAT6J+FhR6evU5V3kGSze4q1MzKXvsq225jf7LZoULNrKaFmm2o9Ns2HkttGEvm2ni+AYPhjEIPz/aIypBrWjKXfU0SkX/dmr2eVa57UwrZrGzPqWw7ey6zfSQi30cr/TY7W6jse/berfI4zp6jyqwkq417t35xjwEAAAAAQCcZkAMAAAAA0EkG5AAAAAAAdJIBOQAAAAAAnWRADgAAAABAJxmQAwAAAADQSQbkAAAAAAB0kgE5AAAAAACdZEAOAAAAAEAnGZADAAAAANBJk/u9AGjTexcvTOWOK9T8i2TuDYWaW+LeVO5ZhZr3JHOzkrkzCtteF6elclsKNaclc0cWas5K5jYWambXWTEpmZtVqLk9mZtaqDmczFXOexvrXJ/MzSzUPDdxrdm5a1fcdOeaQlXottOTPbyijXeG7Er28J2Fmtnr/lihZtZQsoefWaiZO0K1Y9QUslnZ41l5HGXXuatQsw1tPDeyx7PyOG5jnZlrza5du+I7ejiknZHs4UOFmtneeGih5uuTHWp1oeb/J5m7JZnbWtj2nckeXum3m5K5yrU8O4isDCyz/bZy/5B9jdlGb6qco2y2cjzbuMfM3utUztG+rjW7du2K1cn+7R3kAAAAAAB0kgE5AAAAAACdZEAOAAAAAEAnGZADAAAAANBJBuQAAAAAAHSSATkAAAAAAJ1kQA4AAAAAQCcZkAMAAAAA0EkG5AAAAAAAdNLkfi8Aqr64eGE6e0sy9w+F7R+ZzE2Pe9M1v5XM/XK6YsSMZO6RZO62wrZ/MbnvNxVqTorTUrkFhZrZfd9YqHlcMrehUHNLMvdYoeYxyVxlnbOSuUmFmv+azG0t1JyWzDWFmocnMjsK9WCiWljo4W28iyP/vM738LFkrrI/2XVmt12T2/fsvUtExO8ne/gNhZrfLGSzhlqoWeklva5Z2Z9sto39qWjjMZ+p2c5zDQbLokIPz97vTylsf2oyd0ihh/9cMveidMWIZyZz2bnC/YVtPye579cWam5L9vBdhZrTk7ns6+DK9iu9MTsw3Vmome2jlX6bXWell2Wfw5XzntXL+7FKLe8gBwAAAACgkwzIAQAAAADoJANyAAAAAAA6yYAcAAAAAIBOMiAHAAAAAKCTDMgBAAAAAOgkA3IAAAAAADrJgBwAAAAAgE4yIAcAAAAAoJMMyAEAAAAA6KTJ/V4A/Nh/X7wwlfv5Qs2VydwZhZqr495Ubk2h5pRkblWh5hPJ3LOSuSML2/7HZG5SoeYpyeO+uVDzsTgtlTumUPPkZG5boeajyVz2cRQR8aNkblqh5mPJ3NRCzUOTuccLNbckczsLNecUsjARLUz28DbemTFUyI4le0mzf0vZx7Z7r5/vdKls+/3J435YoeaMZA/fWKiZVXnM9bNmGwZlnUDeGckeXhkeZbO1mrleMr9QM/v6aWOhZrY/Znte9vV6RMT3krnnFGpuSR73fy3U/F6yh1d6TvY1ZuU1XvZ+sPL6Nns/WLknys5+KvOX7Ovryv1gdt8r9+G7DvDvf1r5vvrmm2+OV7ziFTF//vwYGhqKL37xi3v8fdM08Xu/93sxb968OOyww+Lcc8+N++67r7oZAKCH9G8AGEx6OAC0qzwg37p1a5x55plx+eWX7/XvP/KRj8THP/7x+OQnPxm33XZbHH744XHeeefFtm2V90sCAL2kfwPAYNLDAaBd5Y9YueCCC+KCCy7Y6981TRMf+9jH4v3vf3+88pWvjIiIz3zmMzFnzpz44he/GK9//esPbLUAwH7RvwFgMOnhANCunn504dq1a2P9+vVx7rnn7v7eyMhILFmyJG655ZZebgoA6BH9GwAGkx4OAAeup7+kc/369RERMWfOnr+ubM6cObv/7meNjo7G6Ojo7j9v3lz5FXsAwIHan/4doYcDQL/p4QBw4Hr6DvL9sWLFihgZGdn9tWDBgn4vCQBI0MMBYDDp4QDwEz0dkM+dOzciIjZs2LDH9zds2LD7737W8uXLY9OmTbu/1q1b18slAQD7sD/9O0IPB4B+08MB4MD1dEB+wgknxNy5c+PGG2/c/b3NmzfHbbfdFkuXLt3r/zM8PBwzZ87c4wsAOHj2p39H6OEA0G96OAAcuPJnkD/22GNx//337/7z2rVr484774zZs2fHcccdF+9+97vjwx/+cJx88slxwgknxO/+7u/G/Pnz41WvelUv1w0AFOjfADCY9HAAaFd5QH7HHXfEL/3SL+3+8yWXXBIRERdddFFcffXV8Vu/9VuxdevWeNvb3hYbN26MF77whXH99dfHoYce2rtVMzDetXhhOntmMjdU2P5T/6PCPW0p1JyazP1ToebrkrnKP3zclcxl/xlJ5Z+bnJHM3V2ouTqZe2ahZvYYHV2o+cNk7sRCzceTubWFmk0yd1ihZnadlefb9mRuUqFmtvFlHx8REQ8lMjsL9dqgf1N1WqGHZ5+DlR6elb2eVbODYCyZa+OXDmW3Xdn+rxRq3pfMPVGoObrvSFn2Md/GY7ON51u/VR53E4keTtWZhR6evUZXhkfZbOW1xhHJ3LJCzQeSuccKNbOvXx5M5uYVtp0969MLNY9N5u4p1Lw8mav08B3JXKXfZvtopTdla1bu3bLPo+zr9Yj8c7iy79nXw/26Xy8PyF/ykpdE0zz1coeGhuJDH/pQfOhDHzqghQEAvaN/A8Bg0sMBoF1tvKEEAAAAAADGPQNyAAAAAAA6yYAcAAAAAIBOMiAHAAAAAKCTDMgBAAAAAOgkA3IAAAAAADrJgBwAAAAAgE4yIAcAAAAAoJMMyAEAAAAA6KTJ/V4Ag2nZ4oWp3LMKNeclc39TqJl3bzp5aDI3s7D1/5PMnV6o+XCPc48Wtn1uMvedQs0tydxooeaLkrnbCzV/lMxtLNR8PJmbXaj5SDI3vVBzWzJXOUe7krmphZo7k7mhQs1pPdwutO30ZA+vyD5fmhZq7ir08Oz2B+UdJIOyzrFk7oZCzRcmcz8o1Mz2xmxvioiY1ELNNlSem1nZx2f28RGRvy5UenhGG8cH9seiZA+v9IfsUKgyPMrem88s9PDfTOZG0hXzr8leXqi5JpnLvsbLvsaKyPecyuvGKcnc/ELNrMpMJfN6LCJiQ6Fm9thXnm/Z41npjTuSucpzeHsyV3mNm+2llR6+r2Nf6d+Dcl8NAAAAAAA9ZUAOAAAAAEAnGZADAAAAANBJBuQAAAAAAHSSATkAAAAAAJ1kQA4AAAAAQCcZkAMAAAAA0EkG5AAAAAAAdJIBOQAAAAAAnTS53wtg/Phfixems7OSuTMK2/9GMjevUHNN3JvKNYWa2ezJhZo/TOZ+UKh5YjJ3WzJ3QmHbhyVzUwo1Zydz2ws1f5R8fMyJ09I1n0jmpqUrRvwomas8jhckcxsKNYeTuUmFmrsK2azsT4Z3Fmo+3uN6ULWw0MOHepyrqDz/dySv0RXZ53/lHSRjLdTstcq2s+dox/4sZB+OKmQPSz4+phV6eLaPZXt9RL6PZR9HEfnzWbkvaONx3MY1JFuzcjz7UQ9+2hmFHp69Rh9a2H52KFTp4Ycnr9H/30LNY9Pbzjsnmav0vOxr9n9O5rKvrSMibk3mnlmouTaZe1ah5u8nHx+/X+jhDydzbQxB27jHq7x2zGYrvSw7V2nj9XrlHO1r+5V7Ie8gBwAAAACgkwzIAQAAAADoJANyAAAAAAA6yYAcAAAAAIBOMiAHAAAAAKCTDMgBAAAAAOgkA3IAAAAAADrJgBwAAAAAgE4yIAcAAAAAoJMMyAEAAAAA6KTJ/V4A48fRhezcZG5VoeYvJXOr4950zZnJ3I/SFSMeSeb+rlDzyGRuU6HmD5O5GcnczsK2/08yN79Qc10yt71QM3su5xVqbknmnijUPCGZy57ziIgdydwzCzUfSOamFGpmDRWy2e1XfoKcOZ+7CvWgqvJ4zWYrNbPXlF2FHj6WzFWe/1mVntfGu00mJXNTk7nssYzIn8vKfs9O5jYXah6XzN1YqNnGdTp77Pv9rqXsY64p1MxmK/teeSxnZa4hbVxn4Mfa6OGV50r28T2p0MPPTuZOTleMGE7mKtfy7OuC7Gu8iPxsIbvtbxW2fXgy951CzewMonIu7yhks7LnvXKPl1V5Dme3X3kOZ/ttpWb2vqDSH7PHqXKO9rXvlfuWft+LAQAAAABAXxiQAwAAAADQSQbkAAAAAAB0kgE5AAAAAACdZEAOAAAAAEAnGZADAAAAANBJBuQAAAAAAHSSATkAAAAAAJ1kQA4AAAAAQCdN7vcCaN/yxQtTuSWFmn8d96Zyw4WaVyVzRxRqPp7MjRVqnpjMbS/UHErmTi/UfCCZyx6jYwrb/oVk7vOFmqcmc5sLNQ9L5rYUaj6RzP18oeZDhWzWvGRuXaHm1GQu+3iPiJiSzG0r1GySOT9BZjxYmOzhkwo1m2QP31mq2dtcReWaMiiyxyl7jnYVtp29J8penyPyj8/fKdS8tpDN2pHMVfY9q3KOsv2pcn/bRs/LPjcr14XsPlX2J1OzjWsXE98vJHt4Ta6HV+4LjkzmKsOj7Ousyuuco5O5fynUPDyZGynUzL4enZHMPbOw7ewMYn2hZvYc/WOh5vRkbmuhZvYxX3luZHtOpYe3odLvs9ro4dnjVDme+9p+ZX1e/wMAAAAA0EnlAfnNN98cr3jFK2L+/PkxNDQUX/ziF/f4+ze96U0xNDS0x9f555/fq/UCAPtB/waAwaSHA0C7ygPyrVu3xplnnhmXX375U2bOP//8ePjhh3d/fe5znzugRQIAB0b/BoDBpIcDQLvKn0F+wQUXxAUXXPC0meHh4Zg7d+5+LwoA6C39GwAGkx4OAO1q5TPIb7rppjjmmGPilFNOiXe84x3x6KOPPmV2dHQ0Nm/evMcXAHDwVfp3hB4OAOOFHg4A+6/nA/Lzzz8/PvOZz8SNN94Y/+2//bdYuXJlXHDBBbFr195/D+mKFStiZGRk99eCBQt6vSQAYB+q/TtCDweA8UAPB4ADU/6IlX15/etfv/u/zzjjjFi0aFGceOKJcdNNN8U555zzpPzy5cvjkksu2f3nzZs3a84AcJBV+3eEHg4A44EeDgAHppWPWPlpz3jGM+Koo46K+++/f69/Pzw8HDNnztzjCwDor3317wg9HADGIz0cAGpaH5B///vfj0cffTTmzZvX9qYAgB7RvwFgMOnhAFBT/oiVxx57bI+fRK9duzbuvPPOmD17dsyePTs++MEPxoUXXhhz586NBx54IH7rt34rTjrppDjvvPN6unAAIE//BoDBpIcDQLvKA/I77rgjfumXfmn3n3/8uWUXXXRRXHHFFXHXXXfFpz/96di4cWPMnz8/Xvayl8V/+S//JYaHh3u3akp+NZm7tVBzfTK3oVBzTjL3fwo1tydzlUfnjGRuR6Hm4cncNws1NyVz2eOe3e+IiD9P5sYKNb+TzDWFmjPitFRubaHm0cnc1kLN7IW68jj+YTJXed/RI8ncU/+6qCc7LJnbWaj5WDJ326rVharjn/49mLL/1K9y7atce7Mq28/K7vtQoWZ2nT3/JT1F2XOUzbXxT0Yr5zx7PzarUPOwZA/PbjsiYnoyl+0jEfnjVDlH2cd8GzXbULkmZffpHj1cDx8Hso/XSs+ZlMxNLdTMXiefX6iZfS2cfc0akX/9srFQ8/vJ3NxCzezrl/nJ3M8Xtj0lmfvXFmpWXJHs4ZV7jezzrdJzsr2x8nzLvhauvL7NXhcqr8Oz26/cP4wmc/3q4eX7/5e85CXRNE/9ML3hhhsOaEEAQO/p3wAwmPRwAGhX659BDgAAAAAA45EBOQAAAAAAnWRADgAAAABAJxmQAwAAAADQSQbkAAAAAAB0kgE5AAAAAACdZEAOAAAAAEAnGZADAAAAANBJk/u9APbPPy5emM4+kcz9S9ybrvm9ZG5numI+O1So2SRzkwo1s8ezYlMy91ihZnbf/zmZy64xImJeMnd/oebUZO6wOC1dc1Yy95x0xYh/SeaGCzXnJnOVn3g+mMyNFmpmz/sjhZqPJ3OV58Ztq1YX0tB7Cws9PGuo0MPHer71/qrsT/YeolKzcg+RlV1nttdncxWHFbIvTeaOLvTwX07mjkhXjPjrZG5KoWa2j1YeR208h7OPkcp9eBvu0cPps+cUenj2fv+QQg/PDnAqrzWOK2Sz/jWZ216omb1O3lWomfVwIZu9ns5M5qYVtp29Rh9eqJnt4f9PoYdnH5/Z14IVbfTbSl/e1cealfvB7HGqzPzGew/3DnIAAAAAADrJgBwAAAAAgE4yIAcAAAAAoJMMyAEAAAAA6CQDcgAAAAAAOsmAHAAAAACATjIgBwAAAACgkwzIAQAAAADoJANyAAAAAAA6yYAcAAAAAIBOmtzvBbB/7itk5yVz9xRqbk3m5hZqPprMbS7UPCyZe6JQc2ohmzWWzB1RqHloMve9ZK5yjB5O5k4o1JyfzN1VqHlUMndvoeYLkrnsMYrIP47/pVAz+xxeUqj5jWRue6FmNjutUBP6rXLzNZTM7SrU7Oe7IyrbzvbGpoXtV2pm19nGTXd2ndk1RuSP0fRCzf87mfvLQs0XJXPfLdTMHqcdhZptPIezNfstezwrj0/ot52FbPaxXXl9ma05pVDz+GRuRqFm9rX9lkLN7Ou8yqykcu3NemYy93gyV+k52ddEzyvU/P1krnLv9Fgy18bspSL7PBot1Myez8rxzKr02+zr8InUw72DHAAAAACATjIgBwAAAACgkwzIAQAAAADoJANyAAAAAAA6yYAcAAAAAIBOMiAHAAAAAKCTDMgBAAAAAOgkA3IAAAAAADrJgBwAAAAAgE6a3O8FsKc/XrwwldtWqLkmmXu0UPPwZO6xQs3NydxQoeaPkrlZhZpZcwrZx5O5yr4/lMxtTOaOLWx7WjK3tlDz5GTumELNnclcZd+3JnO7CjUPTeaOLtTckszdUqh5VDJXOe/Z4/SNVasLVaEdpyd7eOVa3uzfUg667Dsuxgo1s9nKuz2yx7NSM3s+K/uevfa1cYymJHP/V6HmpmTu1ELNZyZzlT6WvS9oQ7+vC9ntV7adza7WwxkHfiHZw7PXyIh2hi3Z6/kThZrfT+bOLtScmsxV1vnPyVzlejqczFXO5YxkLvu6NXssI/IznRMLNf9DMvepQs2symvmynnvdc0dhZrZfarse3b7bazz3gnUw72DHAAAAACATjIgBwAAAACgkwzIAQAAAADoJANyAAAAAAA6yYAcAAAAAIBOMiAHAAAAAKCTDMgBAAAAAOgkA3IAAAAAADrJgBwAAAAAgE4yIAcAAAAAoJMm93sB7GkomVtfqDmazE0q1Mz+ZKUp1PyFZO6hQs0tydyMQs1tydzGQs3scdpRqPl4MrcgmZta2Pa/JXOHFWr+U5yWyj1cqDkrmXt2oebqZG5aoeZYMrexUPPIZO47hZrZ8/lYoeY3VmWPKAyOSm/MPv+zuYp+v4uin9uvHM/sOnfuz0L6YGYy9zeFmv8n2cMXFWq+MZn750LNrOz9ekT++V6p2YbsOiuP49V6OAOkcm+elb3f3lWo2evXeBERS5K5FxRqZmcQXynUzF6njivUPCmZq8xf/i6Ze1Yyt66w7XnJXGWm8vFkD688jvupch/+RDJX6Y3Z7W8v1Mxuv7Lv93awh5dee6xYsSKe97znxYwZM+KYY46JV73qVbFmzZo9Mtu2bYtly5bFkUceGdOnT48LL7wwNmzY0NNFAwA1ejgADCY9HADaVRqQr1y5MpYtWxa33nprfO1rX4sdO3bEy172sti6devuzHve85748pe/HJ///Odj5cqV8dBDD8VrXvOani8cAMjTwwFgMOnhANCu0kesXH/99Xv8+eqrr45jjjkmVq1aFWeffXZs2rQprrzyyrjmmmvipS99aUREXHXVVfGsZz0rbr311nj+85/fu5UDAGl6OAAMJj0cANp1QB/vuGnTpoiImD17dkRErFq1Knbs2BHnnnvu7sypp54axx13XNxyyy17rTE6OhqbN2/e4wsAaJceDgCDSQ8HgN7a7wH52NhYvPvd744XvOAFcfrpp0dExPr162Pq1Kkxa9asPbJz5syJ9ev3/msNVqxYESMjI7u/Fiyo/DoJAKBKDweAwaSHA0Dv7feAfNmyZXHPPffEtddee0ALWL58eWzatGn317p1ld/RCwBU6eEAMJj0cADovdJnkP/YxRdfHF/5ylfi5ptvjmOPPXb39+fOnRvbt2+PjRs37vHT6w0bNsTcuXP3Wmt4eDiGh4f3ZxkAQJEeDgCDSQ8HgHaU3kHeNE1cfPHFcd1118XXv/71OOGEE/b4+8WLF8eUKVPixhtv3P29NWvWxIMPPhhLly7tzYoBgDI9HAAGkx4OAO0qvYN82bJlcc0118SXvvSlmDFjxu7PMxsZGYnDDjssRkZG4i1veUtccsklMXv27Jg5c2a8853vjKVLl/rN2QDQR3o4AAwmPRwA2lUakF9xxRUREfGSl7xkj+9fddVV8aY3vSkiIv7wD/8wDjnkkLjwwgtjdHQ0zjvvvPjEJz7Rk8V2wcZkbmqh5v1xbyq3vVDz0GTuh4WaW5K5yq+PmVTIZmX/2UXln2ccnszNLNTcmMxtTeYeKmx77/+Q88mmFGpmzStks4/PWwo1s8/NynMje97HCjXvS+amFWo+nsx9Y9XqQlV6RQ9v337/YpenMZbs4RXZ3tgUalauP72u2cZxr9jVQs3sPmWPUaXfzk7mjt13ZLf/O5n7z4Wa9ydzlcdx9ri3cc4r62zDzmRutR7eF3p4+55I5ir3xj9K9vD9+tzbfVhSyD4zmav022x/yr5+iMj3snMKNdckc5sLNX+QzP1VMvcfC9v+VjJ3dKHmUCGble15ld64I5mr9PDsPXO2h0bk11mpmXW3Hv60Stfiptn3w/PQQw+Nyy+/PC6//PL9XhQA0Ft6OAAMJj0cANrV7zfdAAAAAABAXxiQAwAAAADQSQbkAAAAAAB0kgE5AAAAAACdZEAOAAAAAEAnGZADAAAAANBJBuQAAAAAAHSSATkAAAAAAJ00ud8L6IJbFy9MZzckc48Utr89mXtmCzUrsjXXFGoekcwNF2qOJHOPF2r+MJl7rFBzczKXXeeswraz5/L4OC1d83vJ3M+nK0aMJXPbCjWnJ3NTCzWzz/fs4ygi/9PRyjqvW7W6kIbBcHqhhw8lc83+LaVn2th+9pqSve5WalZkt1/ZdhvHs3KcMiprzG77HYUe/k/J3PnpihF3JXM7CjWz+559rke089zIns+dhZqr9XAmoMWFHp59XlVe42VfN1Zkrz9/V6iZHQr9a6HmvyRzcws15ydzJxZqZrOXFWqek8ydnsz9W2Hbo8ncbxd6eFblXiP7OG6jh1fWmT2elXVme3NlnXfr4T3hHeQAAAAAAHSSATkAAAAAAJ1kQA4AAAAAQCcZkAMAAAAA0EkG5AAAAAAAdJIBOQAAAAAAnWRADgAAAABAJxmQAwAAAADQSQbkAAAAAAB0kgE5AAAAAACdNLnfC+iC+wvZ4WRuUqHm1GRuV6HmzmRuWqHmtmRuR6HmvyVzhxdqbi1ks55I5n5UqDmWzC1I5rLnPCLiF+O0VO77hZqHJXM/V6i5LplbWqi5NpmrPI6PSOYqP/H8+WTuA6tWF6rCxDPUQs3s9Tki/7yu1GxDG9vP1qxc+9o4ntmaTaFmNpvd9hsK2z4l2cOPKtR8JJn700LN7L1TGyrXhexjqfL4yN6TrdbD6bjKa+Y2ek729W12BhCRH+BUBj0PFrJZuU4SsbhQM7tPldf2/5rM/Wqh5qeTuZOTuS8Utv1XySNfeS2afR5VemO257Vx7zRaqJnd98osLZu9Vw8/6LyDHAAAAACATjIgBwAAAACgkwzIAQAAAADoJANyAAAAAAA6yYAcAAAAAIBOMiAHAAAAAKCTDMgBAAAAAOgkA3IAAAAAADrJgBwAAAAAgE6a3O8FdMGcQnZnMrelUHNaMvdQoeauZG57oWZ23ycVah6azI0Uamb3abhQ8/FkrvKEnZ7MZX9KdlRh248VslmLk7lHCzWPTObWFmpmz9HhhZrfSuYqP/H8wKrVhTR0V+W627Sw/bFkro13PAwVsv18x0X2GEXk19nG/lQeH9ntH53M3VfY9r8lc39aqPlwMle5v80+Ptt4XlZqZtdZeRyv1sMhJfv6MqKd/pDNTinUzF4rKq/H5iZzpxdqLkzmvlyoeVYyd02h5q8nc58p1My+Hr0nmVtX2HblMZ+Vnf1U7pmf2J+F7MOOZK5yjLL9vtLD79XDxy3vIAcAAAAAoJMMyAEAAAAA6CQDcgAAAAAAOsmAHAAAAACATjIgBwAAAACgkwzIAQAAAADoJANyAAAAAAA6yYAcAAAAAIBOMiAHAAAAAKCTDMgBAAAAAOikyf1ewCD768ULU7kjCzUfS+bOKtR8KE5L5Y6Je9M1Vydzk9IVI2YnczsKNXcmc6OFmtuTuey5jIg4KZn7UaHm9B7XPC35OIqI+LlkbixdMWJmMndnoWb2XB5aqLktmdtYqDmSzH1mVfaZCZye7OGV69Su/VvKPuSuvWOFHp5VeRdFk8xVbjzbOJ7Z8zlUqJnd9zbelZK917ir0MOz+/54umJe5fnWz3f5VNaZdY8eDmmLkz28Ivu8rry+PSR57d1R6OHZPlq5Tt2czK0r1My+JvpqoeZf9njbEREPJ3MPFGpmfSqZm1To4dnHR2Wmkr0fy85eIvL3TpV7wcpMJyt7nO7WwyeE0r3lihUr4nnPe17MmDEjjjnmmHjVq14Va9as2SPzkpe8JIaGhvb4evvb397TRQMANXo4AAwmPRwA2lUakK9cuTKWLVsWt956a3zta1+LHTt2xMte9rLYunXrHrm3vvWt8fDDD+/++shHPtLTRQMANXo4AAwmPRwA2lX6iJXrr79+jz9fffXVccwxx8SqVavi7LPP3v39adOmxdy5c3uzQgDggOnhADCY9HAAaNcBfXzfpk2bIiJi9uw9Pz36s5/9bBx11FFx+umnx/Lly+Pxx9v4lEIAYH/p4QAwmPRwAOit/f4lnWNjY/Hud787XvCCF8Tpp5+++/tveMMb4vjjj4/58+fHXXfdFe973/tizZo18YUvfGGvdUZHR2N09Ccfp7958+b9XRIAkKCHA8Bg0sMBoPf2e0C+bNmyuOeee+Lv//7v9/j+2972tt3/fcYZZ8S8efPinHPOiQceeCBOPPHEJ9VZsWJFfPCDH9zfZQAARXo4AAwmPRwAem+/PmLl4osvjq985SvxjW98I4499tinzS5ZsiQiIu6///69/v3y5ctj06ZNu7/WrVu3P0sCABL0cAAYTHo4ALSj9A7ypmnine98Z1x33XVx0003xQknnLDP/+fOO++MiIh58+bt9e+Hh4djeHi4sgwAoEgPB4DBpIcDQLtKA/Jly5bFNddcE1/60pdixowZsX79+oiIGBkZicMOOyweeOCBuOaaa+JXfuVX4sgjj4y77ror3vOe98TZZ58dixYtamUHAIB908MBYDDp4QDQrqGmaZp0eGhor9+/6qqr4k1velOsW7cufv3Xfz3uueee2Lp1ayxYsCBe/epXx/vf//6YOXNmahubN2+OkZGR7JL66q8WL0zlNhZqnr7vSERE/FOh5ui+IxER8eW4N13zsGTuiXTFiGnJ3M5Cze3JXO7R+e92JHN7f7YcmEmFbPbYPyOZmx+npbe99/epPNlj6YoRdydzWwo1Z/U4F5F/vv+3VasLVZlINm3alO6JvaSH72lRsodXruVjyVz6xquQHSr08DZk34NYOZ7ZPlY5noMi+xmIM5K57YUenj2elfuxNu6JsuvMPi8r7tHDO0sPHx+ek+zhFdnr1JRCzexrt8mFHp59h+PUdMV8tnLdz6rUzM41Kq+Zs9uv/FuL7Awi2+unFXp49lftZmcaEfl+W7kfy/bm7LGsbP8uPbyTMv27/BErT2fBggWxcuXKSkkA4CDQwwFgMOnhANCu/folnQAAAAAAMOgMyAEAAAAA6CQDcgAAAAAAOsmAHAAAAACATjIgBwAAAACgkwzIAQAAAADoJANyAAAAAAA6yYAcAAAAAIBOMiAHAAAAAKCTJvd7AePNqxcvTGc3JHObC9u/J5k7rFDz0WTulDgtXXNt3FtYQc6kZG5nz7cccUQh+/1kblah5pHJ3PZCzY3J3FDyvK8rbPvxZO57hZozk7nZhZo/l8w9VKi5q5AFeuv0Qg9vQ9NCzaF0Mt/DI9nDK++iGE3m8vuTz7Zx3NtQOZ7Z7PbkeR8ubDt7PCvHfayQzWrjfhDon+cUeni2P1R6ThvXvrze9/CKx5K5yr5nB02V/pC97ldqZtfZRs85JHnes6+tI/L3Y2305cqsIjv7qTzmvA7nQHkHOQAAAAAAnWRADgAAAABAJxmQAwAAAADQSQbkAAAAAAB0kgE5AAAAAACdZEAOAAAAAEAnGZADAAAAANBJBuQAAAAAAHSSATkAAAAAAJ00ud8LGG/eUch+P5n7SqHmnGTuiELNqcncvxZqvjhOS+VWxb3pmpOSue3pihHTkrldLdTcVKj53GTuG8njHpFfZ/anZI+mtxzxvWTu6ELNx5O5eYWas5K5i1etLlQFBsFQH2s2hZpjPd52RMSkZC8ZK/TwrEq/7afKO0iy5+iQQg/P3hNljRayvd52RP4xX3luZK3Ww2EgtPH831nItjEYqWw/a1uylwwXeni251V6Y3bfK/0pe44qj6XsfcmuQg/Pys41KvOP7D1J5Rhlz2XlXnRHMne3Hs5B5B3kAAAAAAB0kgE5AAAAAACdZEAOAAAAAEAnGZADAAAAANBJBuQAAAAAAHSSATkAAAAAAJ1kQA4AAAAAQCcZkAMAAAAA0EkG5AAAAAAAdJIBOQAAAAAAnTS53wsYb5pCdkMyd0ah5pHJ3KxCze3J3L8Wat6ezJ0Vp6Vr3h33pnJT0hUjnpnMPVCouTiZu7NQc3XyOA0Xam5M5o5J5rKPzYiIHcnc1kLNyvazfn3V6haqAv3Sxk/9K/cFlWw/jaWT+R4eyR5ekT2f+f1pq2blOPVWtt9OaqFmRfZ4Vo77aj0cJpRKD9+ZzFWufdnrT+Uamd3+E4Wa2dfCOwu9aUcLr8Ozg6Y27rN2FWoOJY/TaKFm9rGcfSxlH+8REUPJXBv3rJWad+vhjEPeQQ4AAAAAQCcZkAMAAAAA0EkG5AAAAAAAdJIBOQAAAAAAnWRADgAAAABAJxmQAwAAAADQSQbkAAAAAAB0kgE5AAAAAACdZEAOAAAAAEAnTe73Ag6WLy5emModUag5JZnbUqh5VAs1/66FmrOSuRsKNWfHaanc0YWa/5bMPVGo+WgyN6lQc0Eyd2ShZvbJ/a/J3PbCtmcnc7sKNWckc8tXrS5UBQbB6ckeXtH0vGI72x7qca49uR7ehsq7PcZaqJlVuS/IrrOf5z27xkp2tR4OE84vJHv4zkLN7DW68lojW7PSw7OvnypDmez1tHI8m2QPr/TGHclcdqYSEbEtmav0xuxjpHLes8c+ey77ec9acbcezoDzDnIAAAAAADqpNCC/4oorYtGiRTFz5syYOXNmLF26NL761a/u/vtt27bFsmXL4sgjj4zp06fHhRdeGBs2bOj5ogGAGj0cAAaTHg4A7SoNyI899ti47LLLYtWqVXHHHXfES1/60njlK18Z9957b0REvOc974kvf/nL8fnPfz5WrlwZDz30ULzmNa9pZeEAQJ4eDgCDSQ8HgHYNNU1zQB9pNHv27PiDP/iDeO1rXxtHH310XHPNNfHa1742IiK++93vxrOe9ay45ZZb4vnPf36q3ubNm2NkZORAlrRX2c8gn1+oeXMyd2+h5gnJXOUnG/38DPLK521mP7e68ujIfp7bQ4Waz2qh5inJ3OOFmv38DPJpyVzlcwGzn79+qc8+Y5zbtGlTzJw5s9/LiIjB6eHZzyDv9+fG9fOzJPv/GeQ5lc+tbuN89vMzyCufs1o5ThmV/cn2Zp9BThfp4XXZzyBvoz8MSs+pfAZ5G/uevS9po49VamY/27vfn0Ge3fde5yLauR/M7rvPIGc8y/Tv/e4Du3btimuvvTa2bt0aS5cujVWrVsWOHTvi3HPP3Z059dRT47jjjotbbrnlKeuMjo7G5s2b9/gCANqjhwPAYNLDAaD3ygPyu+++O6ZPnx7Dw8Px9re/Pa677rpYuHBhrF+/PqZOnRqzZs3aIz9nzpxYv379U9ZbsWJFjIyM7P5asGBBeScAgH3TwwFgMOnhANCe8oD8lFNOiTvvvDNuu+22eMc73hEXXXRRrF69//+UYvny5bFp06bdX+vWrdvvWgDAU9PDAWAw6eEA0J7Kx11FRMTUqVPjpJNOioiIxYsXx+233x5/9Ed/FK973eti+/btsXHjxj1+er1hw4aYO3fuU9YbHh6O4eHh+soBgBI9HAAGkx4OAO054N9FMTY2FqOjo7F48eKYMmVK3Hjjjbv/bs2aNfHggw/G0qVLD3QzAECP6eEAMJj0cADondI7yJcvXx4XXHBBHHfccbFly5a45ppr4qabboobbrghRkZG4i1veUtccsklMXv27Jg5c2a8853vjKVLl6Z/czYA0A49HAAGkx4OAO0qDcgfeeSR+I3f+I14+OGHY2RkJBYtWhQ33HBD/PIv/3JERPzhH/5hHHLIIXHhhRfG6OhonHfeefGJT3yilYVXXZnMvbBQc14y9/8Wam5I5rYXag4lcyOFmruSuVmFmtOSuUcLNacmczMKNe9M5iq/5ubfkrnvFmpmVfY9K/uYe2ah5iWr9v8zFoHB7uFjyVzln8Vlr1MV2e1n94fey56jA/4nlgeo6XG97H1bRP7xWXkcr9bD4YAMcg/PXn8qfblyTcsalN68M5mrHM9sdluhZlbluPfzHGWPe0X2uFfuSbLHqPL4uFsPpyOGmqbp9T34Adm8eXOMjFRGtTmvWLwwlWtjQH5VoeZhyVwbA/LKT0uyF94jCzWzZ31zoWZ2QF7Z9x8mc5UB+cxkblAG5NlPMzQgp4s2bdoUM2dmn/WDpa0evjDZwyvX8jYG5NkbqkF5Ed6Gyr73c0jdxrYnFbJtDH+yDMjhqenhdYuSPbzSl9sYYLRxX5DVxuvwNgbkbQyJ29j3NvRzQF45l208Pu7Sw5kAMv2732+QAQAAAACAvjAgBwAAAACgkwzIAQAAAADoJANyAAAAAAA6yYAcAAAAAIBOMiAHAAAAAKCTDMgBAAAAAOgkA3IAAAAAADppcr8XcLCclcydVKj5kWTulELNh5O5BYWak5K5jYWas5K5xws1sw/GaYWaM5O5nYWab0jmNhZqfieZm1WoOT2Ze6xQM2t2MnfJqtUtbB2YaPr50/ymj9tuS/Z4jrW6in3L3hdUengbj6WhZG5XoWb2cZfNtXEuV+vhQJ9kr7ttqNwXZNdZ6WPZ7U9poWZFtuaOQs3s8Wyj51Uec73u4ZX7h+x9zl16ODyJd5ADAAAAANBJBuQAAAAAAHSSATkAAAAAAJ1kQA4AAAAAQCcZkAMAAAAA0EkG5AAAAAAAdJIBOQAAAAAAnWRADgAAAABAJxmQAwAAAADQSQbkAAAAAAB00uR+L+BAvHrxwnT2W8ncA4XtL0jmphZqjiRzDxdqTk/mdhVqPp7MTSrUzO7TkYWaO5K5KYWa2XVOK9Sck8xtLtQcSuayF4HDCtv+/VWrC2mgixYWeng/Za+lFZV3JzTJXL/XOZbMVfptVuVeIyt73CNq90+9lj3uFav1cGAfFhV6eBv9aaKp9Jzs8dy5PwvZh8o627h/qWy/19pYZxs9/G49HPabd5ADAAAAANBJBuQAAAAAAHSSATkAAAAAAJ1kQA4AAAAAQCcZkAMAAAAA0EkG5AAAAAAAdJIBOQAAAAAAnWRADgAAAABAJxmQAwAAAADQSZP7vYADcUwh+2gy90Sh5qnJ3L8Vap6RzK0p1DwlmXuwUHNnMndooeZRydxwoeaCZO6hQs3sT5V+UKiZfdw1hZrrk7nPrVpdqArQG5NaqFm5RmazlXcSVLafld1+ZZ1j+7OQfciezzbWWTnu2ZpDhZq93nbFaj0cGOey1+jKdTdbs42+3EZ/aGPf29DPbfd7+5Uens3eq4fDuOId5AAAAAAAdJIBOQAAAAAAnWRADgAAAABAJxmQAwAAAADQSQbkAAAAAAB0kgE5AAAAAACdZEAOAAAAAEAnGZADAAAAANBJBuQAAAAAAHSSATkAAAAAAJ00ud8LOBDbC9n3JnN/Vai5I5l7YaHmPydzv1iouTmZO6tQc14yd1uh5sZk7qhCzZ3JXFOomTWlkP1OMveZVav3ZykA486uQjb70/yxQs3sDVClPwz1eNsRtX3KauN4tlEzq42alcdn1mo9HJggsv0uIt9H23g91obKvvezN1a0ceyzx6mN+8E23K2Hw4RXusZcccUVsWjRopg5c2bMnDkzli5dGl/96ld3//1LXvKSGBoa2uPr7W9/e88XDQDU6OEAMJj0cABoV+kd5Mcee2xcdtllcfLJJ0fTNPHpT386XvnKV8a3v/3tOO200yIi4q1vfWt86EMf2v3/TJs2rbcrBgDK9HAAGEx6OAC0qzQgf8UrXrHHn//rf/2vccUVV8Stt966uzFPmzYt5s6d27sVAgAHTA8HgMGkhwNAu/b7Y5x27doV1157bWzdujWWLl26+/uf/exn46ijjorTTz89li9fHo8//vjT1hkdHY3Nmzfv8QUAtEcPB4DBpIcDQO+Vf0nn3XffHUuXLo1t27bF9OnT47rrrouFCxdGRMQb3vCGOP7442P+/Plx1113xfve975Ys2ZNfOELX3jKeitWrIgPfvCD+78HAECKHg4Ag0kPB4D2DDVNU/qlxdu3b48HH3wwNm3aFH/xF38Rn/rUp2LlypW7m/NP+/rXvx7nnHNO3H///XHiiSfutd7o6GiMjo7u/vPmzZtjwYIFqbW8efGTt/lU/q9k7q/SFfO/cfnMQs1/TuZmFWpm3wtwZKHmvGTutkLNjcnc/ELN6cnchkLN7Pa3FWrelcx9xm/PhoG3adOmmDlzZl+2PZ56+MJCD8/+c7exdMX9eIdAwlAL267sU69Vtt3GOcrK3o9VtLHO1Xo4DDw9/N+dWejhpWFDj7Wx7co/wW+jN7axT23UzN4TVXr4fn/8QQ/crYfDQMv07/Lrw6lTp8ZJJ50UERGLFy+O22+/Pf7oj/4o/uRP/uRJ2SVLlkREPG1jHh4ejuHh4eoyAIAiPRwABpMeDgDtOeAfwo2Nje3xk+efduedd0ZExLx52fcbAwAHix4OAINJDweA3im9g3z58uVxwQUXxHHHHRdbtmyJa665Jm666aa44YYb4oEHHohrrrkmfuVXfiWOPPLIuOuuu+I973lPnH322bFo0aK21g8AJOjhADCY9HAAaFdpQP7II4/Eb/zGb8TDDz8cIyMjsWjRorjhhhvil3/5l2PdunXxt3/7t/Gxj30stm7dGgsWLIgLL7ww3v/+9+/Xwt747FNi6qRJT5t5+r/d05eTuZcXau795/VPdkSh5i8kc8cVamY/1+uhQs3sZ3ZPLdQ8JpnbWaj59L+7/Scqj6XvJXP/3eeUAePIwezhpzz7lJi0jx7ehn5+NmVl+5U+llX5/NDs54JmcxH5e43KOtv4TNTs57z6vHBgPDmYPfz0RA/v97U825/a+H0SbfTGiuzxbOP3iFRkt185nlk+LxzYH6UB+ZVXXvmUf7dgwYJYuXLlAS8IAOg9PRwABpMeDgDt6vebrQAAAAAAoC8MyAEAAAAA6CQDcgAAAAAAOsmAHAAAAACATjIgBwAAAACgkwzIAQAAAADoJANyAAAAAAA6yYAcAAAAAIBOMiAHAAAAAKCTJvd7AU9l0v//q1dmJHMbCjVfncx9slBzRzK3pFBzbTK3ulDzF5O5Rws1z0rmVhVqzk7mNhVqfnxV5UgBsDeVn9CPtVBzZws1s4YK2V3JXGWdTY+3XZHddkT+OGUfHxERq/VwgANWuZb3c/tt9MY2VLbdxj1RpY/2WmWdd+vhQIu8gxwAAAAAgE4yIAcAAAAAoJMMyAEAAAAA6CQDcgAAAAAAOsmAHAAAAACATjIgBwAAAACgkwzIAQAAAADoJANyAAAAAAA6yYAcAAAAAIBOmtzvBTyVH0bElH1kZhXq/SiZ+2ah5spk7hcLNW9J5v6lUHMomTumUPN/J3PTCjXvSOYqD9pNydzHV60uVAXgQI1NwJrZbBvvTthZyPbz5q9pIbtaDwfomZ1Ru1aPZ9nXwdVsVhvHcVIL287uexs179bDgXHCO8gBAAAAAOgkA3IAAAAAADrJgBwAAAAAgE4yIAcAAAAAoJMMyAEAAAAA6CQDcgAAAAAAOsmAHAAAAACATjIgBwAAAACgkwzIAQAAAADoJANyAAAAAAA6aXK/F/BU/vrONfvM/OLihel6m5K5ygGZlszdWKi5KJnbUKiZ3ac7CzWzxzObi4h4LJn7i1WrC1UBONjWJHr4wkIPn2jGCtk23smws4WaWav1cIBx7buJHn5aoYc3ydxQumK+N1b6bRvrrGSzsuus7HvWvXo4MIF5BzkAAAAAAJ1kQA4AAAAAQCcZkAMAAAAA0EkG5AAAAAAAdJIBOQAAAAAAnWRADgAAAABAJxmQAwAAAADQSQbkAAAAAAB0kgE5AAAAAACdNLnfCzgQ31y1Op190eKFPd/+UDI3r1DzpmTuRYWatyVzjxZqbk/mdhZq3lA4nwAMttWFa/7CFnr4oBjr9wKSKucTgMF2bws9vClsv5+9MTsDqKjsz6RkrvJOyLv1cIADewf5ZZddFkNDQ/Hud7979/e2bdsWy5YtiyOPPDKmT58eF154YWzYsOFA1wkA9JAeDgCDR/8GgN7b7wH57bffHn/yJ38SixYt2uP773nPe+LLX/5yfP7zn4+VK1fGQw89FK95zWsOeKEAQG/o4QAwePRvAGjHfg3IH3vssXjjG98Y/+t//a844ogjdn9/06ZNceWVV8ZHP/rReOlLXxqLFy+Oq666Kr75zW/Grbfe2rNFAwD7Rw8HgMGjfwNAe/ZrQL5s2bJ4+ctfHueee+4e31+1alXs2LFjj++feuqpcdxxx8Utt9xyYCsFAA6YHg4Ag0f/BoD2lH9J57XXXhvf+ta34vbbb3/S361fvz6mTp0as2bN2uP7c+bMifXr1++13ujoaIyOju7+8+bNm6tLAgAS9HAAGDy97t8RejgA/LTSO8jXrVsX73rXu+Kzn/1sHHrooT1ZwIoVK2JkZGT314IFC3pSFwD4CT0cAAZPG/07Qg8HgJ9WGpCvWrUqHnnkkXjOc54TkydPjsmTJ8fKlSvj4x//eEyePDnmzJkT27dvj40bN+7x/23YsCHmzp2715rLly+PTZs27f5at27dfu8MALB3ejgADJ42+neEHg4AP630ESvnnHNO3H333Xt8781vfnOceuqp8b73vS8WLFgQU6ZMiRtvvDEuvPDCiIhYs2ZNPPjgg7F06dK91hweHo7h4eH9XD4AkKGHA8DgaaN/R+jhAPDTSgPyGTNmxOmnn77H9w4//PA48sgjd3//LW95S1xyySUxe/bsmDlzZrzzne+MpUuXxvOf//zerRoAKNHDAWDw6N8A0L7yL+nclz/8wz+MQw45JC688MIYHR2N8847Lz7xiU/0ejMAQI/p4QAwePRvADgwQ03TNP1exE/bvHlzjIyM9G37L1q8MJ3N/oO0WYXtH5bM7SrUfLzHuYiIv1m1upAG4Mc2bdoUM2fO7PcyWtHvHr6w0MP7qfILYMZa2P5qPRxgv+jh7Wmjhw/1vGL/3auHA5Rl+nfpl3QCAAAAAMBEYUAOAAAAAEAnGZADAAAAANBJBuQAAAAAAHSSATkAAAAAAJ1kQA4AAAAAQCcZkAMAAAAA0EkG5AAAAAAAdNLkfi/gZzVN09ft79y1K52dlMztKGw/W3OsUDO7/Z2FmgDsn373uTb1e992FXp4P1WOUqXfA9Cufve5NvV739ro4UM9rwjAIMr0uHE3IN+yZUtft3/LnWv6un0AJrYtW7bEyMhIv5fRin738DV6OAAt0sPbo4cD0JZM/x5q+v2j4p8xNjYWDz30UMyYMSOGhn7yM9/NmzfHggULYt26dTFz5sw+rrA37M/4N9H2yf6MfxNtn+zPnpqmiS1btsT8+fPjkEMm5iec7a2HT7THQcTE2yf7M/5NtH2yP+PfRNsnPXzf9PDBZH/Gt4m2PxETb5/sz/h3IPtU6d/j7h3khxxySBx77LFP+fczZ86cMCc5wv4Mgom2T/Zn/Jto+2R/fmKivuvsx56uh0+0x0HExNsn+zP+TbR9sj/j30TbJz38qenhg83+jG8TbX8iJt4+2Z/xb3/3Kdu/J+aPvwEAAAAAYB8MyAEAAAAA6KSBGZAPDw/HpZdeGsPDw/1eSk/Yn/Fvou2T/Rn/Jto+2R8iJuZxm2j7ZH/Gv4m2T/Zn/Jto+zTR9udgmYjHbaLtk/0Z3yba/kRMvH2yP+PfwdqncfdLOgEAAAAA4GAYmHeQAwAAAABALxmQAwAAAADQSQbkAAAAAAB0kgE5AAAAAACdNBAD8ssvvzx+/ud/Pg499NBYsmRJ/OM//mO/l7TfPvCBD8TQ0NAeX6eeemq/l5V28803xyte8YqYP39+DA0NxRe/+MU9/r5pmvi93/u9mDdvXhx22GFx7rnnxn333defxSbsa3/e9KY3Pel8nX/++f1ZbMKKFSviec97XsyYMSOOOeaYeNWrXhVr1qzZI7Nt27ZYtmxZHHnkkTF9+vS48MILY8OGDX1a8dPL7M9LXvKSJ52jt7/97X1a8b5dccUVsWjRopg5c2bMnDkzli5dGl/96ld3//0gnZ+Ife/PoJ2fn3XZZZfF0NBQvPvd7979vUE7R/02UXr4oPfvCD1cDz+49PDxfX4i9PBBOEf9poePH3q4Hn4wTbQePtH6d4Qe3sZ5GvcD8j/7sz+LSy65JC699NL41re+FWeeeWacd9558cgjj/R7afvttNNOi4cffnj319///d/3e0lpW7dujTPPPDMuv/zyvf79Rz7ykfj4xz8en/zkJ+O2226Lww8/PM4777zYtm3bQV5pzr72JyLi/PPP3+N8fe5znzuIK6xZuXJlLFu2LG699db42te+Fjt27IiXvexlsXXr1t2Z97znPfHlL385Pv/5z8fKlSvjoYceite85jV9XPVTy+xPRMRb3/rWPc7RRz7ykT6teN+OPfbYuOyyy2LVqlVxxx13xEtf+tJ45StfGffee29EDNb5idj3/kQM1vn5abfffnv8yZ/8SSxatGiP7w/aOeqnidbDB7l/R+jhevjBpYeP7/MToYcPwjnqJz18fNHD9fCDaaL18InWvyP08FbOUzPOnXXWWc2yZct2/3nXrl3N/PnzmxUrVvRxVfvv0ksvbc4888x+L6MnIqK57rrrdv95bGysmTt3bvMHf/AHu7+3cePGZnh4uPnc5z7XhxXW/Oz+NE3TXHTRRc0rX/nKvqynFx555JEmIpqVK1c2TfPv52PKlCnN5z//+d2Z73znO01ENLfccku/lpn2s/vTNE3z4he/uHnXu97Vv0X1wBFHHNF86lOfGvjz82M/3p+mGdzzs2XLlubkk09uvva1r+2xDxPlHB0sE6mHT6T+3TR6+CDQwweDHj7+6OG9oYePX3r4+KeHj38TrX83jR5+oMb1O8i3b98eq1atinPPPXf39w455JA499xz45Zbbunjyg7MfffdF/Pnz49nPOMZ8cY3vjEefPDBfi+pJ9auXRvr16/f43yNjIzEkiVLBvp83XTTTXHMMcfEKaecEu94xzvi0Ucf7feS0jZt2hQREbNnz46IiFWrVsWOHTv2OEennnpqHHfccQNxjn52f37ss5/9bBx11FFx+umnx/Lly+Pxxx/vx/LKdu3aFddee21s3bo1li5dOvDn52f358cG8fwsW7YsXv7yl+9xLiIG/zl0ME3EHj5R+3eEHj4e6eHjmx4+funhB04PHyx6+Pijh49fE61/R+jhvTpPk3tSpSU//OEPY9euXTFnzpw9vj9nzpz47ne/26dVHZglS5bE1VdfHaeccko8/PDD8cEPfjBe9KIXxT333BMzZszo9/IOyPr16yMi9nq+fvx3g+b888+P17zmNXHCCSfEAw88EL/zO78TF1xwQdxyyy0xadKkfi/vaY2NjcW73/3ueMELXhCnn356RPz7OZo6dWrMmjVrj+wgnKO97U9ExBve8IY4/vjjY/78+XHXXXfF+973vlizZk184Qtf6ONqn97dd98dS5cujW3btsX06dPjuuuui4ULF8add945kOfnqfYnYjDPz7XXXhvf+ta34vbbb3/S3w3yc+hgm2g9fCL37wg9fLzRw8dvj9DDx/f50cN7Qw8fLHr4+KKHj88eMdH6d4QeHtHb8zSuB+QT0QUXXLD7vxctWhRLliyJ448/Pv78z/883vKWt/RxZezN61//+t3/fcYZZ8SiRYvixBNPjJtuuinOOeecPq5s35YtWxb33HPPwH2+3lN5qv1529vetvu/zzjjjJg3b16cc8458cADD8SJJ554sJeZcsopp8Sdd94ZmzZtir/4i7+Iiy66KFauXNnvZe23p9qfhQsXDtz5WbduXbzrXe+Kr33ta3HooYf2ezmMI/r34NHDxw89fPzSw+kCPXzw6OHjx0Tp4ROtf0fo4b02rj9i5aijjopJkyY96beSbtiwIebOndunVfXWrFmz4pnPfGbcf//9/V7KAfvxOZnI5+sZz3hGHHXUUeP+fF188cXxla98Jb7xjW/Escceu/v7c+fOje3bt8fGjRv3yI/3c/RU+7M3S5YsiYgY1+do6tSpcdJJJ8XixYtjxYoVceaZZ8Yf/dEfDez5ear92Zvxfn5WrVoVjzzySDznOc+JyZMnx+TJk2PlypXx8Y9/PCZPnhxz5swZyHPUDxO9h0+k/h2hh48nevj47RERenjE+D0/enjv6OGDRQ8fP/Tw8dsjJlr/jtDDI3p7nsb1gHzq1KmxePHiuPHGG3d/b2xsLG688cY9PldnkD322GPxwAMPxLx58/q9lAN2wgknxNy5c/c4X5s3b47bbrttwpyv73//+/Hoo4+O2/PVNE1cfPHFcd1118XXv/71OOGEE/b4+8WLF8eUKVP2OEdr1qyJBx98cFyeo33tz97ceeedERHj9hztzdjYWIyOjg7c+XkqP96fvRnv5+ecc86Ju+++O+68887dX8997nPjjW984+7/ngjn6GCY6D18IvXvCD18PNDDx3+P2Bs9fPzQw3tHDx8senj/6eHjv0f8rInWvyP08APWk1/12aJrr722GR4ebq6++upm9erVzdve9rZm1qxZzfr16/u9tP3ym7/5m81NN93UrF27tvmHf/iH5txzz22OOuqo5pFHHun30lK2bNnSfPvb326+/e1vNxHRfPSjH22+/e1vN//yL//SNE3TXHbZZc2sWbOaL33pS81dd93VvPKVr2xOOOGE5oknnujzyvfu6fZny5YtzXvf+97mlltuadauXdv87d/+bfOc5zynOfnkk5tt27b1e+l79Y53vKMZGRlpbrrppubhhx/e/fX444/vzrz97W9vjjvuuObrX/96c8cddzRLly5tli5d2sdVP7V97c/999/ffOhDH2ruuOOOZu3atc2XvvSl5hnPeEZz9tln93nlT+23f/u3m5UrVzZr165t7rrrrua3f/u3m6GhoeZv/uZvmqYZrPPTNE+/P4N4fvbmZ38D+KCdo36aSD180Pt30+jhevjBpYeP7/PTNHr4IJyjftLDxxc9XA8/mCZaD59o/btp9PA2ztO4H5A3TdP88R//cXPcccc1U6dObc4666zm1ltv7feS9tvrXve6Zt68ec3UqVObn/u5n2te97rXNffff3+/l5X2jW98o4mIJ31ddNFFTdM0zdjYWPO7v/u7zZw5c5rh4eHmnHPOadasWdPfRT+Np9ufxx9/vHnZy17WHH300c2UKVOa448/vnnrW986rm8K97YvEdFcddVVuzNPPPFE85/+039qjjjiiGbatGnNq1/96ubhhx/u36Kfxr7258EHH2zOPvvsZvbs2c3w8HBz0kknNf/5P//nZtOmTf1d+NP4j//xPzbHH398M3Xq1Oboo49uzjnnnN2NuWkG6/w0zdPvzyCen7352cY8aOeo3yZKDx/0/t00ergefnDp4eP7/DSNHj4I56jf9PDxQw/Xww+midbDJ1r/bho9vI3zNNQ0TbP/7z8HAAAAAIDBNK4/gxwAAAAAANpiQA4AAAAAQCcZkAMAAAAA0EkG5AAAAAAAdJIBOQAAAAAAnWRADgAAAABAJxmQAwAAAADQSQbkAAAAAAB0kgE5AAAAAACdZEAOAAAAAEAnGZADAAAAANBJBuQAAAAAAHTS/w8SWNqBUDCJdQAAAABJRU5ErkJggg==", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "cells_to_plot = [0, 99, 310]\n", + "fig, axes = plt.subplots(2, len(cells_to_plot), figsize=(15, 10))\n", + "\n", + "for idx, i in enumerate(cells_to_plot):\n", + " # Plotting original cell\n", + " plot_cell_image(cell_objects[i], channels=['nucleus', 'protein'], ax=axes[0, idx])\n", + " axes[0, idx].set_title(f'Original Cell {i}')\n", + " \n", + " # Plotting mapped cell\n", + " mapped_cell_object = cell_objects[target_cell_ind].copy()\n", + " for j, channel in enumerate(channels_to_map):\n", + " mapped_cell_object.intensities[channel] = mapped_distbs[j][i]\n", + " plot_cell_image(mapped_cell_object, channels=['nucleus', 'protein'], ax=axes[1, idx])\n", + " axes[1, idx].set_title(f'Mapped Cell {i}')\n", + "\n", + "plt.tight_layout()\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "9f0a8a95", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Mapping cells to target cell:\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "373it [19:59, 3.22s/it] \n" + ] + } + ], + "source": [ + "# We choose the morphological centroid cell as the anchor cell to map to\n", + "target_cell_ind = find_centroid(gw_dmat)\n", + "\n", + "channels_to_map = ['protein'] # which distributions to quantify variation in localization patterns for\n", + "# Mapping all cells to anchor cell\n", + "mapped_distbs = map_to_cell_parallel(cell_objects, \n", + " channels_to_map, \n", + " target_cell_ind, # cell to map to\n", + " method='fused', # 'fused' for full mapping, 'fused' for partial mapping\n", + " fused_channel='nucleus', # addition info to consider for mapping\n", + " fused_cost=1000, fused_param=0.1, # controls weight of additional info\n", + " compartment_specific=True, # enforces strict mapping of nucleus to nucleus\n", + " num_processes=cpu_count(), chunksize=1) # parallelization parameters" + ] + }, + { + "cell_type": "markdown", + "id": "56480efc", + "metadata": {}, + "source": [ + "We can visualize some examples of the mapped to localalization patterns to see whether the mapping parameters need adjustment." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "90dde1e3", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAABcgAAAPmCAYAAADQQXwHAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjYsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvq6yFwwAAAAlwSFlzAAAPYQAAD2EBqD+naQAA2VBJREFUeJzs3Xl4VOX9//9XEsgkkSyEJYskgIBA2CxRIVUpAhpQESUuuKKlohaogFYbP25QNVStohbi8uELthpRFLRqhQIfCVqBSoSCUKlQKCgkKDYL2wST8/vDX0ZGEs59JjOZSeb5uK65ruTc77nP+ywz98w997lPhGVZlgAAAAAAAAAACDORwU4AAAAAAAAAAIBgoIMcAAAAAAAAABCW6CAHAAAAAAAAAIQlOsgBAAAAAAAAAGGJDnIAAAAAAAAAQFiigxwAAAAAAAAAEJboIAcAAAAAAAAAhCU6yAEAAAAAAAAAYYkOcgAAAAAAAABAWKKDHGhCDz30kCIiInx67oIFCxQREaFdu3b5N6nj7Nq1SxEREVqwYEHA1nEy9e2fLl266KabbgpKPgCA8ENbfXK01QCAUEB7fXK014AzdJADBrZs2aLrr79ep556qlwul9LT03Xddddpy5YtwU4tqMrKynTXXXepV69eiouL0ymnnKLs7Gw9/PDDKi8vb/J8/vznP2vgwIGKiYlRZmamHnzwQX333XdNngcAoOnRVtcvlNrqgwcPaurUqerUqZNcLpd69+6twsLCemOXL1+uc889V3FxcWrbtq2uuOKKgHZkAACaBu11/UKpvZ42bZoGDhyo5ORkxcXFqXfv3nrooYd08OBBr7iDBw/qwQcf1MiRI5WcnGz7g8A///lPjRw5Um3atFFycrJuuOEGff311wHeGsBMq2AnAIS6xYsX65prrlFycrImTJigrl27ateuXZo3b57eeOMNLVy4UJdffrlRXffdd59+85vf+JTHDTfcoHHjxsnlcvn0fH/75JNPdNFFF+ngwYO6/vrrlZ2dLUlav369Zs2apdWrV+uvf/1rk+Xz/vvv67LLLtPQoUP17LPPavPmzXr44Ye1f//+Br98AwBaBtrq+oVSW11TU6Pc3FytX79ekyZNUo8ePbRs2TL98pe/1H//+1/de++9nth3331XY8aM0cCBAzVr1ixVVlbq6aef1rnnnqsNGzaoQ4cOTZIzAMC/aK/rF0rtdV0+5513nm6++WbFxMRow4YNmjVrllasWKHVq1crMvL7sbbffPONZs6cqczMTA0YMECrVq1qsM4vv/xSQ4YMUWJioh599FEdPHhQTzzxhDZv3qy///3vio6ObqKtAxpgAWjQ9u3brbi4OKtXr17W/v37vcq+/vprq1evXtYpp5xi7dix46T1HDx4MJBp+s3OnTstSdb8+fNPGvff//7XOvXUU62UlBTrn//85wnlpaWl1m9/+1vH63/wwQetH78tde7c2Ro/frztc7OysqwBAwZYx44d8yz7n//5HysiIqLeHAEALQNtdf1Cra1+/fXXLUnWvHnzvJbn5eVZMTExVllZmWdZVlaW1b17d8vtdnuWbdy40YqMjLSmT5/uOGcAQPDRXtcv1NrrhjzxxBOWJGvNmjWeZUePHrX27dtnWZZlffLJJyfd3ttvv92KjY21/vOf/3iWLV++3JJkPf/88z7lBPgTU6wAJ/H444/r8OHDeuGFF04YrdS+fXs9//zzOnTokB577DHP8rq5vrZu3aprr71Wbdu21bnnnutVdrwjR47oV7/6ldq3b6/4+Hhdeuml+uqrrxQREaGHHnrIE1ffPGldunTRJZdcoo8++khnn322YmJidNppp+mPf/yj1zq+/fZb3XXXXerXr5/atGmjhIQEjRo1Sv/4xz982i/PP/+8vvrqKz355JPq1avXCeUpKSm67777vJa9//77Ou+883TKKacoPj5eF198sd8uo9u6dau2bt2qiRMnqlWrHy6M+eUvfynLsvTGG2/4ZT0AgNBDW12/UGurP/zwQ0nSuHHjvJaPGzdOR48e1dtvvy3p+/2wdetWXX755V6jyQYMGKDevXtr4cKFfskHANC0aK/rF2rtdUO6dOkiSV7TvbhcLqWmpho9/80339Qll1yizMxMz7IRI0bo9NNP1+uvv+7PVAGf0EEOnMQ777yjLl266Lzzzqu3fMiQIerSpYvee++9E8quvPJKHT58WI8++qhuueWWBtdx00036dlnn9VFF12k3/3ud4qNjdXFF19snOP27dt1xRVX6IILLtDvf/97tW3bVjfddJNXA/nvf/9bb731li655BI9+eST+vWvf63NmzfrZz/7mfbu3Wu8rjp//vOfFRsbqyuuuMIo/k9/+pMuvvhitWnTRr/73e90//33a+vWrTr33HP9Mp/ohg0bJElnnnmm1/L09HR16tTJUw4AaHloq+sXam212+1WVFTUCZdQx8XFSZJKSko8cZIUGxt7Qh1xcXHau3evSktLG50PAKBp0V7XL9Ta6zrfffedvvnmG+3du1d//etfdd999yk+Pl5nn32247q++uor7d+//4Tv65J09tln830dIYE5yIEGVFRUaO/evRozZsxJ4/r3768///nPqqqqUnx8vGf5gAEDVFRUdNLnfvrpp3r99dc1depUPfXUU5K+H/V88803G/8CvW3bNq1evdrzQeOqq65SRkaG5s+fryeeeEKS1K9fP/3rX//yzBUmfT/vWq9evTRv3jzdf//9Ruuq889//lOnn3660TxhBw8e1K9+9Sv94he/0AsvvOBZPn78ePXs2VOPPvqo13Jf7Nu3T5KUlpZ2QllaWppPH1QAAKGPtrphodZW9+zZUzU1NVq7dq1n9J/0w8jyr776StL3I+WSkpL0t7/9zev5Bw4c0NatWz2xpiPWAADBR3vdsFBrr+usX79eOTk5nv979uypP//5z0pOTnZcl9339W+//VZutztk5oRHeGIEOdCAqqoqSfJqmOtTV15ZWem1/LbbbrNdx9KlSyV933Afb8qUKcZ5ZmVlef0K36FDB/Xs2VP//ve/PctcLpenAa+pqdGBAwfUpk0b9ezZU59++qnxuupUVlba7pc6y5cvV3l5ua655hp98803nkdUVJQGDRqkDz74wPH6f+zIkSOSVG+DGhMT4ykHALQstNUNC7W2+tprr1ViYqJ+/vOfa/ny5dq1a5deeOEFzZ07V9IPbXlkZKRuvfVWrVy5Uvn5+friiy9UUlKiq666StXV1V6xAIDmgfa6YaHWXtfJysrS8uXL9dZbb+nuu+/WKaecooMHD/pUl9339eNjgGBhBDnQgLpGqq4xb0hDjX3Xrl1t1/Gf//xHkZGRJ8R2797dOM/j5/Cq07ZtW/33v//1/F9bW6unn35ac+fO1c6dO1VTU+Mpa9eunfG66iQkJNjulzpffPGFJGnYsGEN1tVYdZdh112WfbyjR4/We5k2AKD5o61uWKi11ampqfrzn/+sG264QRdeeKGn3meffVbjx49XmzZtPLEzZ87UN998o8cee0yzZs2SJF144YWaMGGCnnvuOa9YAEDoo71uWKi118fXNWLECEnSmDFjVFRU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9frrrys2NjaImSFYTnZOTJgwIYiZIZDGjRvn+btfv37q37+/unXrplWrVmn48OFBzMw/wvGzTkPbPHHiRM/f/fr1U1pamoYPH64dO3aoW7duTZ2mX4Tj566GtjkrK6tFHuNwRZsc2ppb29lS2sLm3r61lDarubdDLbGPIWSnWGnfvr2ioqJOuMNpWVmZUlNTg5RVy1O3L9nPvpk8ebLeffddffDBB+rUqZNneWpqqqqrq1VeXu4Vz341Ex0dre7duys7O1sFBQUaMGCAnn76afarj0pKSrR//34NHDhQrVq1UqtWrVRcXKxnnnlGrVq1UkpKCvvVT5KSknT66adr+/btnK+iLZe8z4lwwOeK75122mlq3759izju4fhZp6Ftrs+gQYMkqVkf63D83NXQNtenJRxjJ1py290S2uSW3M6GctvZUtrCltC+tZQ2q7m3Qy2xjyFkO8ijo6OVnZ2tlStXepbV1tZq5cqVXvPyoHG6du2q1NRUr/1cWVmpdevWsZ9PwrIsTZ48WUuWLNH//d//qWvXrl7l2dnZat26tdd+3bZtm3bv3s1+9UFtba3cbjf71UfDhw/X5s2btXHjRs/jzDPP1HXXXef5m/3qHwcPHtSOHTuUlpbG+Sracsn7nAgHfK743pdffqkDBw406+Mejp917La5Phs3bpSkZn2sfywcP3fVbXN9WuIxPpmW3Ha3hDa5Jbezodh2tpS2sCW3by2lzWpu7VCL7GMI6i1CbSxcuNByuVzWggULrK1bt1oTJ060kpKSrNLS0mCn1qxUVVVZGzZssDZs2GBJsp588klrw4YN1n/+8x/Lsixr1qxZVlJSkvX2229bmzZtssaMGWN17drVOnLkSJAzD1233367lZiYaK1atcrat2+f53H48GFPzG233WZlZmZa//d//2etX7/eysnJsXJycoKYdfPwm9/8xiouLrZ27txpbdq0yfrNb35jRUREWH/9618ty2K/+suP74zNfvXNnXfeaa1atcrauXOn9be//c0aMWKE1b59e2v//v2WZbFfLSv82nK7c6IlCMfPFSfb5qqqKuuuu+6y1qxZY+3cudNasWKFNXDgQKtHjx7W0aNHg526z8Lxs47dNm/fvt2aOXOmtX79emvnzp3W22+/bZ122mnWkCFDgpy578Lxc9fJtrklHmNftJS2u7m2yS2lnW0JbWdLaQtbSvvWUtqsltoONfc+hpDuILcsy3r22WetzMxMKzo62jr77LOttWvXBjulZueDDz6wJJ3wGD9+vGVZllVbW2vdf//9VkpKiuVyuazhw4db27ZtC27SIa6+/SnJmj9/vifmyJEj1i9/+Uurbdu2VlxcnHX55Zdb+/btC17SzcTPf/5zq3PnzlZ0dLTVoUMHa/jw4Z4Gz7LYr/7y48aL/eqbq6++2kpLS7Oio6OtU0891br66qut7du3e8rZr98Lp7bc7pxoCcLxc8XJtvnw4cPWhRdeaHXo0MFq3bq11blzZ+uWW25pdh1JPxaOn3Xstnn37t3WkCFDrOTkZMvlclndu3e3fv3rX1sVFRXBTbwRwvFz18m2uSUeY1+1hLa7ubbJLaWdbQltZ0tpC1tK+9ZS2qyW2g419z6GCMuyLP+MRQcAAAAAAAAAoPkI2TnIAQAAAAAAAAAIJDrIAQAAAAAAAABhiQ5yAAAAAAAAAEBYooMcAAAAAAAAABCW6CAHAAAAAAAAAIQlOsgBAAAAAAAAAGGJDnIAAAAAAAAAQFiigxwAAAAAAAAAEJboIAfgZejQoRo6dGhQ1r1r1y5FRERowYIFnmUPPfSQIiIigpIPAAChivYaAIDQRlsNNB90kAMNWLBggSIiIhQREaGPPvrohHLLspSRkaGIiAhdcsklQcgw+GpqajR//nwNHTpUycnJcrlc6tKli26++WatX7++yfP56quvdNVVVykpKUkJCQkaM2aM/v3vfzd5HgCApkN7bS/U2uuFCxdq4MCBiomJUYcOHTRhwgR98803J8SVlZXp5ptvVseOHRUbG6uBAwdq0aJFTZ4vAKBxaKvthVJbvWTJEuXm5io9PV0ul0udOnXSFVdcoc8+++yE2Ndee03XX3+9evTooYiIiJP+IOB2u3XPPfcoPT1dsbGxGjRokJYvXx7ALQHM0UEO2IiJiVFRUdEJy4uLi/Xll1/K5XIFIavgO3LkiC655BL9/Oc/l2VZuvfee1VYWKgbb7xRa9as0dlnn60vv/yyyfI5ePCgzj//fBUXF+vee+/VjBkztGHDBv3sZz/TgQMHmiwPAEBw0F7XL9Ta68LCQl1zzTVKTk7Wk08+qVtuuUULFy7U8OHDdfToUU9cZWWlzj33XL355pu69dZb9cQTTyg+Pl5XXXVVvccZABD6aKvrF2pt9ebNm9W2bVvdcccdmjt3rm6//XZt2LBBZ599tv7xj394xRYWFurtt99WRkaG2rZte9J6b7rpJj355JO67rrr9PTTTysqKkoXXXRRvT+aAE2tVbATAELdRRddpEWLFumZZ55Rq1Y/vGSKioqUnZ1d74incPDrX/9aS5cu1VNPPaWpU6d6lT344IN66qmnmjSfuXPn6osvvtDf//53nXXWWZKkUaNGqW/fvvr973+vRx99tEnzAQA0Ldrr+oVSe11dXa17771XQ4YM0fLlyz2Xef/0pz/V6NGj9eKLL2rKlCmSpOeff17bt2/XypUrNWzYMEnS7bffrsGDB+vOO+/UFVdcoejo6CbLHQDQeLTV9QultlqSHnjggROW/eIXv1CnTp1UWFio5557zrP8T3/6k0499VRFRkaqb9++Ddb597//XQsXLtTjjz+uu+66S5J04403qm/fvrr77rv18ccf+39DAAcYQQ7YuOaaa3TgwAGvS3+qq6v1xhtv6Nprr633OU888YR++tOfql27doqNjVV2drbeeOONE+IiIiI0efJkvfLKK+rZs6diYmKUnZ2t1atXe8XVzRX2+eef66qrrlJCQoLatWunO+64w2u0VZ2XX35Z2dnZio2NVXJyssaNG6c9e/acEPfCCy+oW7duio2N1dlnn60PP/zQaJ98+eWXev7553XBBRec0IBLUlRUlO666y516tTJs+yrr77Sz3/+c6WkpMjlcqlPnz76f//v/xmtz8Qbb7yhs846y9M5Lkm9evXS8OHD9frrr/ttPQCA0ER7faJQa68/++wzlZeX6+qrr/aaA/WSSy5RmzZttHDhQs+yDz/8UB06dPB0jktSZGSkrrrqKpWWlqq4uNgvOQEAmg5t9YlCra1uSMeOHRUXF6fy8nKv5RkZGYqMtO9afOONNxQVFaWJEyd6lsXExGjChAlas2ZNvfsUaEp0kAM2unTpopycHL366queZe+//74qKio0bty4ep/z9NNP6yc/+YlmzpypRx99VK1atdKVV16p995774TY4uJiTZ06Vddff71mzpypAwcOaOTIkfXO73XVVVfp6NGjKigo0EUXXaRnnnnGq4GRpEceeUQ33nijevTooSeffFJTp07VypUrNWTIEK/GbN68ebr11luVmpqqxx57TOecc44uvfRSo4bp/fff13fffacbbrjBNlb6fg7RwYMHa8WKFZo8ebKefvppde/eXRMmTNDs2bON6jiZ2tpabdq0SWeeeeYJZWeffbZ27NihqqqqRq8HABC6aK9PFGrttdvtliTFxsaeUBYbG6sNGzaotrbWE1tfXFxcnCSppKSk0fkAAJoWbfWJQq2tPl55ebm+/vprbd68Wb/4xS9UWVmp4cOH+1TXhg0bdPrppyshIcFr+dlnny1J2rhxY2PTBRrHAlCv+fPnW5KsTz75xPrDH/5gxcfHW4cPH7Ysy7KuvPJK6/zzz7csy7I6d+5sXXzxxV7PrYurU11dbfXt29caNmyY13JJliRr/fr1nmX/+c9/rJiYGOvyyy/3LHvwwQctSdall17q9fxf/vKXliTrH//4h2VZlrVr1y4rKirKeuSRR7ziNm/ebLVq1cqzvLq62urYsaN1xhlnWG632xP3wgsvWJKsn/3sZyfdN9OmTbMkWRs2bDhpXJ0JEyZYaWlp1jfffOO1fNy4cVZiYqJnf+3cudOSZM2fP/+EbT+Zr7/+2pJkzZw584SyOXPmWJKszz//3ChXAEDzQnvdsFBsryMiIqwJEyZ4Lf/88889+7hu3VOmTLEiIyOtXbt2nZCLJGvy5MlG2wQACD7a6oaFWlt9vJ49e3r2a5s2baz77rvPqqmpaTC+T58+DW5vnz59TjhmlmVZW7ZssSRZzz33nHFeQCAwghwwcNVVV+nIkSN69913VVVVpXfffbfBS8Ak75FR//3vf1VRUaHzzjtPn3766QmxOTk5ys7O9vyfmZmpMWPGaNmyZaqpqfGKnTRpktf/dfN0/uUvf5EkLV68WLW1tbrqqqv0zTffeB6pqanq0aOHPvjgA0nS+vXrtX//ft12221e83fedNNNSkxMtN0flZWVkqT4+HjbWMuy9Oabb2r06NGyLMsrr9zcXFVUVNS7X5w4cuSIJNV7U5eYmBivGABAy0V77S3U2uv27dvrqquu0ksvvaTf//73+ve//60PP/xQV199tVq3bi3ph/b6F7/4haKionTVVVfp448/1o4dO1RQUKAlS5Z4xQEAmhfaam+h1lYfb/78+Vq6dKnmzp2r3r1768iRIyfsR1NHjhzh+zpCGjfpBAx06NBBI0aMUFFRkQ4fPqyamhpdccUVDca/++67evjhh7Vx40bP5cSSvObbrNOjR48Tlp1++uk6fPiwvv76a6WmpjYY261bN0VGRmrXrl2SpC+++EKWZdVbpyTPl8///Oc/9dbXunVrnXbaaQ1uV526y6JMpi35+uuvVV5erhdeeEEvvPBCvTH79++3redk6j40Hb+v69TNI1ffZdoAgJaF9tpbqLXX0vc33zxy5Ijuuusuz026rr/+enXr1k2LFy9WmzZtJEn9+/dXUVGRbrvtNp1zzjmSpNTUVM2ePVu33367Jw4A0LzQVnsLxba6Tk5OjufvcePGqXfv3pK+nxfeqdjYWL6vI6TRQQ4Yuvbaa3XLLbeotLRUo0aNUlJSUr1xH374oS699FINGTJEc+fOVVpamlq3bq358+erqKjIrzn9+ENBbW2tIiIi9P777ysqKuqEeH99mezVq5ckafPmzTrjjDNOGls3l+j111+v8ePH1xvTv3//RuWTnJwsl8ulffv2nVBWtyw9Pb1R6wAANA+01z8ItfZakhITE/X2229r9+7d2rVrlzp37qzOnTvrpz/9qTp06OB1vK644gpdeuml+sc//qGamhoNHDhQq1atkvR9hwcAoHmirf5BKLbV9Wnbtq2GDRumV155xacO8rS0NH311VcnLOf7OkIFHeSAocsvv1y33nqr1q5dq9dee63BuDfffFMxMTFatmyZ1yVE8+fPrzf+iy++OGHZv/71L8XFxalDhw4nxHbt2tXz//bt21VbW6suXbpI+v5Xb8uy1LVr15N+cezcubOnvmHDhnmWHzt2TDt37tSAAQMafK4kjRo1SlFRUXr55ZdtbybSoUMHxcfHq6amRiNGjDhprK8iIyPVr18/rV+//oSydevW6bTTTjO6ZA0A0PzRXv8g1Nrr42VmZiozM1PS9zcBKykpUV5e3glx0dHROuusszz/r1ixQpKaJEcAQGDQVv8glNvqHzty5IgqKip8eu4ZZ5yhDz74QJWVlV436ly3bp2nHAgm5iAHDLVp00aFhYV66KGHNHr06AbjoqKiFBER4TU3165du/TWW2/VG79mzRqvecL27Nmjt99+WxdeeOEJv1TPmTPH6/9nn31W0veNqiSNHTtWUVFRmjFjhizL8oq1LEsHDhyQJJ155pnq0KGDnnvuOVVXV3tiFixY4HU37oZkZGTolltu0V//+ldPDserra3V73//e3355ZeKiopSXl6e3nzzzXrvHv7111/brs/EFVdcoU8++cSrk3zbtm36v//7P1155ZV+WQcAIPTRXv8gFNvr+uTn5+u7777TtGnTThr3xRdf6LnnntMll1zCCHIAaMZoq38Qim11fdO07Nq1SytXrtSZZ57pU51XXHGFampqvKaGcbvdmj9/vgYNGqSMjAyf8wX8gRHkgAMNXcZ0vIsvvlhPPvmkRo4cqWuvvVb79+/XnDlz1L17d23atOmE+L59+yo3N1e/+tWv5HK5NHfuXEnSjBkzTojduXOnLr30Uo0cOVJr1qzRyy+/rGuvvdbzq3S3bt308MMPKz8/X7t27dJll12m+Ph47dy5U0uWLNHEiRN11113qXXr1nr44Yd16623atiwYbr66qu1c+dOzZ8/32ieNEn6/e9/rx07duhXv/qVFi9erEsuuURt27bV7t27tWjRIn3++ecaN26cJGnWrFn64IMPNGjQIN1yyy3KysrSt99+q08//VQrVqzQt99+a7TOk/nlL3+pF198URdffLFnG5/8/9r792i76vJe/H82uWwISXYIl1xKoAgIBgjWKDFV0QoVqMfhBTu02l/R40+/eoJflXpq02GLejwNx55hrT2IPUcO6Lcira1o9VuhFiW0FShEOVyiKdBYYiFBqbkQyM5lz98f/RmNBPI8yZpZe+35eo2xxyA7b575mXOuNZ+5nr2y9kc/GnPmzInf/M3fPOD6AAwO/fonxlu/vuyyy+Kee+6JJUuWxOTJk+OLX/xi/M3f/E18+MMf3uOd4hERCxcujF/91V+N4447LtauXRtXXHFFzJ49Oz75yU8e8DoA6C+9+ifGW68+44wz4pxzzolnP/vZccQRR8R9990XV155ZezYsSMuu+yyPbI333xz3HzzzRHx7wP6rVu3xoc//OGIiDj77LPj7LPPjoiIJUuWxK/+6q/G8uXL45FHHomTTjopPv3pT8f3vve9uPLKKw94zXDAGmCvrrrqqiYimttvv/1pc8cff3zz8pe/fI/vXXnllc3JJ5/cDA8PN6eeempz1VVXNZdeemnzs0+5iGiWLVvW/Omf/unu/C/8wi803/jGN/bI/fj/Xb16dfPa1762mTFjRnPEEUc0F198cfPEE088aU1/+Zd/2bzwhS9sDj/88Obwww9vTj311GbZsmXNmjVr9sh94hOfaE444YRmeHi4ee5zn9vcfPPNzYtf/OLmxS9+ceoY7dy5s/nUpz7VvOhFL2pGRkaa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7979vUE7R/02UXr4oPfvCD1cDz+49PDxfX4i9PBBOEf9poePH3q4Hn4wTbQePtH6d4Qe3sZ5GvcD8j/7sz+LSy65JC699NL41re+FWeeeWacd9558cgjj/R7afvttNNOi4cffnj319///d/3e0lpW7dujTPPPDMuv/zyvf79Rz7ykfj4xz8en/zkJ+O2226Lww8/PM4777zYtm3bQV5pzr72JyLi/PPP3+N8fe5znzuIK6xZuXJlLFu2LG699db42te+Fjt27IiXvexlsXXr1t2Z97znPfHlL385Pv/5z8fKlSvjoYceite85jV9XPVTy+xPRMRb3/rWPc7RRz7ykT6teN+OPfbYuOyyy2LVqlVxxx13xEtf+tJ45StfGffee29EDNb5idj3/kQM1vn5abfffnv8yZ/8SSxatGiP7w/aOeqnidbDB7l/R+jhevjBpYeP7/MToYcPwjnqJz18fNHD9fCDaaL18InWvyP08FbOUzPOnXXWWc2yZct2/3nXrl3N/PnzmxUrVvRxVfvv0ksvbc4888x+L6MnIqK57rrrdv95bGysmTt3bvMHf/AHu7+3cePGZnh4uPnc5z7XhxXW/Oz+NE3TXHTRRc0rX/nKvqynFx555JEmIpqVK1c2TfPv52PKlCnN5z//+d2Z73znO01ENLfccku/lpn2s/vTNE3z4he/uHnXu97Vv0X1wBFHHNF86lOfGvjz82M/3p+mGdzzs2XLlubkk09uvva1r+2xDxPlHB0sE6mHT6T+3TR6+CDQwweDHj7+6OG9oYePX3r4+KeHj38TrX83jR5+oMb1O8i3b98eq1atinPPPXf39w455JA499xz45Zbbunjyg7MfffdF/Pnz49nPOMZ8cY3vjEefPDBfi+pJ9auXRvr16/f43yNjIzEkiVLBvp83XTTTXHMMcfEKaecEu94xzvi0Ucf7feS0jZt2hQREbNnz46IiFWrVsWOHTv2OEennnpqHHfccQNxjn52f37ss5/9bBx11FFx+umnx/Lly+Pxxx/vx/LKdu3aFddee21s3bo1li5dOvDn52f358cG8fwsW7YsXv7yl+9xLiIG/zl0ME3EHj5R+3eEHj4e6eHjmx4+funhB04PHyx6+Pijh49fE61/R+jhvTpPk3tSpSU//OEPY9euXTFnzpw9vj9nzpz47ne/26dVHZglS5bE1VdfHaeccko8/PDD8cEPfjBe9KIXxT333BMzZszo9/IOyPr16yMi9nq+fvx3g+b888+P17zmNXHCCSfEAw88EL/zO78TF1xwQdxyyy0xadKkfi/vaY2NjcW73/3ueMELXhCnn356RPz7OZo6dWrMmjVrj+wgnKO97U9ExBve8IY4/vjjY/78+XHXXXfF+973vlizZk184Qtf6ONqn97dd98dS5cujW3btsX06dPjuuuui4ULF8add945kOfnqfYnYjDPz7XXXhvf+ta34vbbb3/S3w3yc+hgm2g9fCL37wg9fLzRw8dvj9DDx/f50cN7Qw8fLHr4+KKHj88eMdH6d4QeHtHb8zSuB+QT0QUXXLD7vxctWhRLliyJ448/Pv78z/883vKWt/RxZezN61//+t3/fcYZZ8SiRYvixBNPjJtuuinOOeecPq5s35YtWxb33HPPwH2+3lN5qv1529vetvu/zzjjjJg3b16cc8458cADD8SJJ554sJeZcsopp8Sdd94ZmzZtir/4i7+Iiy66KFauXNnvZe23p9qfhQsXDtz5WbduXbzrXe+Kr33ta3HooYf2ezmMI/r34NHDxw89fPzSw+kCPXzw6OHjx0Tp4ROtf0fo4b02rj9i5aijjopJkyY96beSbtiwIebOndunVfXWrFmz4pnPfGbcf//9/V7KAfvxOZnI5+sZz3hGHHXUUeP+fF188cXxla98Jb7xjW/Escceu/v7c+fOje3bt8fGjRv3yI/3c/RU+7M3S5YsiYgY1+do6tSpcdJJJ8XixYtjxYoVceaZZ8Yf/dEfDez5ear92Zvxfn5WrVoVjzzySDznOc+JyZMnx+TJk2PlypXx8Y9/PCZPnhxz5swZyHPUDxO9h0+k/h2hh48nevj47RERenjE+D0/enjv6OGDRQ8fP/Tw8dsjJlr/jtDDI3p7nsb1gHzq1KmxePHiuPHGG3d/b2xsLG688cY9PldnkD322GPxwAMPxLx58/q9lAN2wgknxNy5c/c4X5s3b47bbrttwpyv73//+/Hoo4+O2/PVNE1cfPHFcd1118XXv/71OOGEE/b4+8WLF8eUKVP2OEdr1qyJBx98cFyeo33tz97ceeedERHj9hztzdjYWIyOjg7c+XkqP96fvRnv5+ecc86Ju+++O+68887dX8997nPjjW984+7/ngjn6GCY6D18IvXvCD18PNDDx3+P2Bs9fPzQw3tHDx8senj/6eHjv0f8rInWvyP08APWk1/12aJrr722GR4ebq6++upm9erVzdve9rZm1qxZzfr16/u9tP3ym7/5m81NN93UrF27tvmHf/iH5txzz22OOuqo5pFHHun30lK2bNnSfPvb326+/e1vNxHRfPSjH22+/e1vN//yL//SNE3TXHbZZc2sWbOaL33pS81dd93VvPKVr2xOOOGE5oknnujzyvfu6fZny5YtzXvf+97mlltuadauXdv87d/+bfOc5zynOfnkk5tt27b1e+l79Y53vKMZGRlpbrrppubhhx/e/fX444/vzrz97W9vjjvuuObrX/96c8cddzRLly5tli5d2sdVP7V97c/999/ffOhDH2ruuOOOZu3atc2XvvSl5hnPeEZz9tln93nlT+23f/u3m5UrVzZr165t7rrrrua3f/u3m6GhoeZv/uZvmqYZrPPTNE+/P4N4fvbmZ38D+KCdo36aSD180Pt30+jhevjBpYeP7/PTNHr4IJyjftLDxxc9XA8/mCZaD59o/btp9PA2ztO4H5A3TdP88R//cXPcccc1U6dObc4666zm1ltv7feS9tvrXve6Zt68ec3UqVObn/u5n2te97rXNffff3+/l5X2jW98o4mIJ31ddNFFTdM0zdjYWPO7v/u7zZw5c5rh4eHmnHPOadasWdPfRT+Np9ufxx9/vHnZy17WHH300c2UKVOa448/vnnrW986rm8K97YvEdFcddVVuzNPPPFE85/+039qjjjiiGbatGnNq1/96ubhhx/u36Kfxr7258EHH2zOPvvsZvbs2c3w8HBz0kknNf/5P//nZtOmTf1d+NP4j//xPzbHH398M3Xq1Oboo49uzjnnnN2NuWkG6/w0zdPvzyCen7352cY8aOeo3yZKDx/0/t00ergefnDp4eP7/DSNHj4I56jf9PDxQw/Xww+midbDJ1r/bho9vI3zNNQ0TbP/7z8HAAAAAIDBNK4/gxwAAAAAANpiQA4AAAAAQCcZkAMAAAAA0EkG5AAAAAAAdJIBOQAAAAAAnWRADgAAAABAJxmQAwAAAADQSQbkAAAAAAB0kgE5AAAAAACdZEAOAAAAAEAnGZADAAAAANBJBuQAAAAAAHTS/w8SWNqBUDCJdQAAAABJRU5ErkJggg==", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "cells_to_plot = [0, 99, 310]\n", + "fig, axes = plt.subplots(2, len(cells_to_plot), figsize=(15, 10))\n", + "\n", + "for idx, i in enumerate(cells_to_plot):\n", + " # Plotting original cell\n", + " plot_cell_image(cell_objects[i], channels=['nucleus', 'protein'], ax=axes[0, idx])\n", + " axes[0, idx].set_title(f'Original Cell {i}')\n", + " \n", + " # Plotting mapped cell\n", + " mapped_cell_object = cell_objects[target_cell_ind].copy()\n", + " for j, channel in enumerate(channels_to_map):\n", + " mapped_cell_object.intensities[channel] = mapped_distbs[j][i]\n", + " plot_cell_image(mapped_cell_object, channels=['nucleus', 'protein'], ax=axes[1, idx])\n", + " axes[1, idx].set_title(f'Mapped Cell {i}')\n", + "\n", + "plt.tight_layout()\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "a459d477", + "metadata": {}, + "source": [ + "After mapping all cells to the anchor cell, we can compute the optimal transport (Wasserstein) distance to measure the difference in protein localization patterns between cells. Similar to the Gromov-Wasserstein morphology space, we can cluster cells based on the optimal transport localization space to identify groups of cells with similar protein localization patterns, and visualize the space with UMAP." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "4917bb17", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Computing pairwise OT distances:\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "100%|██████████| 69378/69378 [20:54<00:00, 55.29it/s] \n" + ] + } + ], + "source": [ + "ot_dmats = gw_mapped_ot_pairwise_parallel(cell_objects[target_cell_ind], mapped_distbs, num_processes=cpu_count(), chunksize=20)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "696ee320", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/opt/conda/lib/python3.10/site-packages/umap/umap_.py:1780: UserWarning:\n", + "\n", + "using precomputed metric; inverse_transform will be unavailable\n", + "\n", + "/opt/conda/lib/python3.10/site-packages/plotly/express/_core.py:1992: FutureWarning:\n", + "\n", + "When grouping with a length-1 list-like, you will need to pass a length-1 tuple to get_group in a future version of pandas. Pass `(name,)` instead of `name` to silence this warning.\n", + "\n" + ] + }, + { + "data": { + "text/html": [ + " \n", + " " + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Compute UMAP representation of the OT localization space\n", + "reducer = umap.UMAP(metric=\"precomputed\", random_state=1)\n", + "embedding = reducer.fit_transform(ot_dmats[0])\n", + "\n", + "# Cluster cells based on the OT localization space using the leiden algorithm\n", + "ot_clusters = cajal.utilities.leiden_clustering(ot_dmats[0], resolution=0.005, seed=1)\n", + "\n", + "# Visualize the OT localization space\n", + "plotly.express.scatter(x=embedding[:,0],\n", + " y=embedding[:,1],\n", + " template=\"simple_white\",\n", + " hover_name=[\"cell_\" + str(i) for i in range(ot_dmats[0].shape[0])],\n", + " color = [str(c) for c in ot_clusters]\n", + " )" + ] + }, + { + "cell_type": "markdown", + "id": "13f8e5cb", + "metadata": {}, + "source": [ + "We can visualize some example cells and their protein distributions from cluster 0." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "839bec6f", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "cells_to_plot = [335, 270, 309] # Example cells from cluster 0\n", + "fig, axes = plt.subplots(1, len(cells_to_plot), figsize=(15, 5))\n", + "for ax, i in zip(axes, cells_to_plot):\n", + " plot_cell_image(cell_objects[i], channels=['nucleus', 'protein'], ax=ax)\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "ed7eb1dc", + "metadata": {}, + "source": [ + "We can also color the UMAP representation of the optimal transport localization space by the localization pattern annotations from the Human Protein Atlas. As expected, the annotated localization patterns separate in the localization space." + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "77c4d262", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/opt/conda/lib/python3.10/site-packages/plotly/express/_core.py:1992: FutureWarning:\n", + "\n", + "When grouping with a length-1 list-like, you will need to pass a length-1 tuple to get_group in a future version of pandas. Pass `(name,)` instead of `name` to silence this warning.\n", + "\n" + ] + }, + { + "data": { + "text/html": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plotly.express.scatter(x=embedding[:,0],\n", + " y=embedding[:,1],\n", + " template=\"simple_white\",\n", + " hover_name=np.array([\"cell_\" + str(i) for i in range(ot_dmats[0].shape[0])]),\n", + " color = np.array([str(c) for c in cell_metadata['locations']])\n", + " )" + ] + }, + { + "cell_type": "markdown", + "id": "86df85ac", + "metadata": {}, + "source": [ + "## Applying existing cell image analysis methods to mapped cells" + ] + }, + { + "cell_type": "markdown", + "id": "064876c8", + "metadata": {}, + "source": [ + "In this tutorial, we use optimal transport distances to quantify differences in subcellular protein localization after mapping each cell to the anchor cell morphology. However, you can apply any existing cell image analysis method (such as CellProfiler or Cytoself) to the mapped cells. To use external tools, we can the mapped cells as individual images for downstream processing." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7de842c7", + "metadata": {}, + "outputs": [], + "source": [ + "output_dir = '/path/to/save/mapped/cell/images/'\n", + "cell_image_channels = ['nucleus', 'protein']\n", + "for i in range(len(cell_objects)):\n", + " # Create a cell object for the mapped cell\n", + " mapped_cell_object = cell_objects[anchor_cell_ind].copy()\n", + " for j, channel in enumerate(channels_to_map):\n", + " mapped_cell_object.intensities[channel] = mapped_distbs[j][i]\n", + " # Generate cell image from mapped cell object\n", + " mapped_cell_image = make_cell_image(mapped_cell_object, channels=cell_image_channels)\n", + " # Write mapped cell image to file\n", + " ski.io.imsave(os.path.join(output_dir, f'mapped_cell_image_{i}.tif'), mapped_cell_image)" + ] + }, + { + "cell_type": "markdown", + "id": "c37a71ec", + "metadata": {}, + "source": [ + "Note, the cell images generated by the `make_cell_image` function are multi-channel images where the first channel is the cell segmentation mask, and subsequent channels are specified by the `channels` parameter." + ] + }, + { + "cell_type": "markdown", + "id": "8b7a9703", + "metadata": {}, + "source": [ + "## Utilizing multiple anchor cells" + ] + }, + { + "cell_type": "markdown", + "id": "0930f006", + "metadata": {}, + "source": [ + "One characteristic of the CellAligner algorithm is that localization analysis following the anchor cell mapping can be dependent on the choice of anchor cell to map to. While in practice we've observed that choosing centroid cell based on the GW morphology space results in informative localization analyses, one may want their analysis to be more robust to the choice of anchor cell. To address this, we suggest mapping the protein distributions of each cell to multiple anchor cells, and integrating the resulting localization spaces. One natural way to select a set of anchor cells is to first cluster the GW morphology space and select the centroid cell of each morphological cluster, thus utilizing a broad range of cellular morphologies in constructing each localization space. Here we utilize OT to construct separate localization spaces for each anchor cell. Note, since this approach involves repeating the GW-based mapping and OT computations per each anchor cell, it will substatially increase the runtime of the analysis." + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "a0d31aeb", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Find centroid cell for each GW morphology cluster\n", + "cluster_centroids = []\n", + "for c in np.unique(gw_clusters):\n", + " cluster_inds = np.where(gw_clusters == c)[0]\n", + " # subset the GW distance matrix to only the cells in the cluster\n", + " gw_dmat_cluster = gw_dmat[np.ix_(cluster_inds, cluster_inds)]\n", + " # find the centroid cell in the cluster\n", + " cluster_centroid_idx = find_centroid(gw_dmat_cluster)\n", + " cluster_centroid = cluster_inds[cluster_centroid_idx]\n", + " cluster_centroids.append(cluster_centroid)\n", + "\n", + "# Visualize centroid cells for each GW morphology cluster\n", + "fig, axes = plt.subplots(1, len(cluster_centroids), figsize=(15, 5))\n", + "for ax, i in zip(axes, cluster_centroids):\n", + " plot_cell_image(cell_objects[i], channels=['nucleus'], ax=ax)\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "60e26557", + "metadata": {}, + "outputs": [], + "source": [ + "centroid_mapped_distbs = []\n", + "centroid_ot_dmats = []\n", + "for target_cell_ind in cluster_centroids[1:]:\n", + " # Mapping all cells to anchor cell\n", + " mapped_distbs = map_to_cell_parallel(cell_objects, \n", + " channels_to_map, \n", + " target_cell_ind, # cell to map to\n", + " method='fused', # 'fused' for full mapping, 'fused' for partial mapping\n", + " fused_channel='nucleus', # addition info to consider for mapping\n", + " fused_cost=1000, fused_param=0.1, # controls weight of additional info\n", + " compartment_specific=True, # enforces strict mapping of nucleus to nucleus\n", + " num_processes=cpu_count(), chunksize=1) # parallelization parameters\n", + " # Compute OT distance matrix for the mapped protein distributions\n", + " ot_dmats = gw_mapped_ot_pairwise_parallel(cell_objects[target_cell_ind], mapped_distbs, num_processes=cpu_count(), chunksize=20)\n", + " centroid_mapped_distbs.append(mapped_distbs)\n", + " centroid_ot_dmats.append(ot_dmats)\n", + "\n", + " out_dir = '/path/to/save/anchor/mapped/distances/'\n", + " for target_cell_ind, mapped_distbs, ot_dmats in zip(cluster_centroids, centroid_mapped_distbs, centroid_ot_dmats):\n", + " with open(os.path.join(out_dir, f'anchor_{target_cell_ind}_mapped_distbs.pickle'), 'wb') as f:\n", + " pickle.dump(mapped_distbs, f, protocol=pickle.HIGHEST_PROTOCOL)\n", + " with open(os.path.join(out_dir, f'anchor_{target_cell_ind}_ot_dmats.pickle'), 'wb') as f:\n", + " pickle.dump(ot_dmats, f, protocol=pickle.HIGHEST_PROTOCOL)" + ] + }, + { + "cell_type": "markdown", + "id": "3c0b69e4", + "metadata": {}, + "source": [ + "Having computed the OT localization spaces for each cluster centroid, we will now build a consolidated space that integrates information from each localization space. For this purpose, we use the Weighted Nearest Neighbors (WNN) algorithm introduced in:\n", + "\n", + "\\- Hao, Y. et al. [Integrated analysis of multimodal single-cell data.](https://www.sciencedirect.com/science/article/pii/S0092867421005833) Cell 184, 3573-3587 (2021).\n", + "\n", + "To do this, we construct instances of the `Modality` class for each input. The input to `cajal.wnn.wnn()` is a list of Modality objects and a number of nearest neighbors to consider in each space." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "dd369efe", + "metadata": {}, + "outputs": [], + "source": [ + "import cajal.wnn\n", + "\n", + "# Extract protein OT dmat from each centroid\n", + "centroid_protein_ot_dmats = [ot_dmats[0] for ot_dmats in centroid_ot_dmats]\n", + "# Integrate OT localization spaces from each centroid cell using WNN\n", + "integrated_space = 1-cajal.wnn.wnn(centroid_protein_ot_dmats, 5)" + ] + }, + { + "cell_type": "markdown", + "id": "4e979675", + "metadata": {}, + "source": [ + "The similarity function returned by the weighted nearest neighbors algorithm is asymmetric. For this reason, the term \"space\" here is somewhat imprecise. To visualize the consolidated space using UMAP, it is therefore convenient to symmetrize the matrix." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "64fe8dd2", + "metadata": {}, + "outputs": [], + "source": [ + "def symmetrize(a):\n", + " a = a.copy()\n", + " a[a == 0] = np.max(a)\n", + " b = a + a.T\n", + " b = np.minimum(a,b)\n", + " b = np.minimum(b,a.T)\n", + " d=np.zeros(a.shape[0],dtype=int)\n", + " b[d,d]=0\n", + " return np.array(b)\n", + "\n", + "wnn_dmat = symmetrize(integrated_space)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "64dd0a71", + "metadata": {}, + "outputs": [], + "source": [ + "# Compute UMAP representation of the OT localization space\n", + "reducer = umap.UMAP(metric=\"precomputed\", random_state=1)\n", + "embedding = reducer.fit_transform(wnn_dmat)\n", + "\n", + "# Visualize the OT localization space\n", + "plotly.express.scatter(x=embedding[:,0],\n", + " y=embedding[:,1],\n", + " template=\"simple_white\",\n", + " hover_name=[\"cell_\" + str(i) for i in range(wnn_dmat.shape[0])],\n", + " color = [str(c) for c in cell_metadata['locations']]\n", + " )" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/notebooks/Example_7.ipynb b/docs/notebooks/Example_7.ipynb new file mode 100644 index 0000000..04ba9ea --- /dev/null +++ b/docs/notebooks/Example_7.ipynb @@ -0,0 +1,7830 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "65da9095", + "metadata": {}, + "source": [ + "# Tutorial 7: Quantifying subcellular protein localization in very large datasets (dCellAligner-OT)" + ] + }, + { + "cell_type": "markdown", + "id": "1b6d513c", + "metadata": {}, + "source": [ + "The Fused Gromov-Wasserstein mapping between two cells with 1000 points takes around 3 s to compute, and the optimal transport (OT) distance between the two mapped distributions takes aroud 18 ms. While number of Fused Gromov-Wasserstein mapping computations scales linearly with the number of cells, the number of Wasserstein computations of mapped distributions scales quadratically, which can result in very long runtimes in datasets with 100s of thousands of cells. For these large datasets, we've provided a deep learning framework, deep CellAligner Optimal Transport (dCellAligner-OT) to reduce the necessary computation. This approach enables users to compute the CellAligner mappings and OT distances for only a subset of cells, and train a deep learning model to predict the mappings and distances for the remaining cells. \n", + "\n", + "We will demonstrate this approach on a dataset of 16,787 neurons with simulated subcellular protein distributions. For this analysis, we assume that the image data has already been processed into CellAligner cell objects, which can be downloaded from this [link](https://www.dropbox.com/scl/fi/mb1wx32lfqiqpu3mkhni9/sim_neuron_cell_objects.zip?rlkey=113rcvxp1qgpp0wbih63phu5t&dl=0)." + ] + }, + { + "cell_type": "markdown", + "id": "dd0b2ff4", + "metadata": {}, + "source": [ + "First, we must convert the cell objects, as well as the mapped subcellular protein distributions, into cell-specific images that the dGW-OT model can take as input. The `make_NN_training_data` function creates two directories (`cell_images` and `mapped_cell_images`) to store the cell and mapped cell images." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4ca74919", + "metadata": {}, + "outputs": [], + "source": [ + "data_path = '/path/to/saved/cell/objects'\n", + "cell_object_paths = [os.path.join(data_path, fname) for fname in os.listdir(data_path)]\n", + "anchor_ind = 658 # index of anchor cell (which other cells are mapped to)\n", + "anchor_cell_obj_path = cell_object_paths[anchor_ind]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6ddb8254", + "metadata": {}, + "outputs": [], + "source": [ + "cell_image_path = '/path/to/saved/cell/images'\n", + "make_NN_training_data(save_path=cell_image_path, # path to saved cell images\n", + " cell_objects=cell_object_paths,\n", + " reference_cell_object=anchor_cell_obj_path,\n", + " mapped_channel_distributions=mapped_distbs[0], # using the mapped protein distribution\n", + " channel='protein', # this should match the mapped distributions used\n", + " center='nucleus', # center='cell' when using Fused GW mappings, center='nucleus' when using Unbalanced Fused GW mappings\n", + " shape=(256,256), # shape of the output cell images\n", + " rescale=False) # rescale=True when using Fused GW mappings, rescale=False when using Unbalanced Fused GW mappings" + ] + }, + { + "cell_type": "markdown", + "id": "1b03bd80", + "metadata": {}, + "source": [ + "To avoid the model overfitting to the training dataset, we split our data into a training, validation, and test set. Since, the dCellAligner model does not need to trained on every pair of training cells, the OT distances between the mapped protein distributions are only computed for a subset of pairs. In practice, we've observed good model performance when training on around 10,000 cells and 30,000 cell pairs. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b18b026f", + "metadata": {}, + "outputs": [], + "source": [ + "# Generate train/val/test dataset cell pairs\n", + "train_pairs, val_pairs, test_pairs = generate_dataset_split_pairs(indices=list(range(len(cell_object_paths))), \n", + " n_pairs=[35000, 10000, 5000], # number of cell pairs in train/val/test sets\n", + " proportions=[0.7, 0.2, 0.1]) # proportion of cells in train/val/test sets\n", + "\n", + "# Store unique indices in each set\n", + "train_inds = np.unique(train_pairs)\n", + "val_inds = np.unique(val_pairs)\n", + "test_inds = np.unique(test_pairs)\n", + "\n", + "# Compute GW-mapped OT distances for all pairs in train/val/test sets\n", + "train_ot_dists = gw_mapped_ot_pairwise_parallel(cell_object_paths[anchor_ind], mapped_distbs, \n", + " num_processes=12, chunksize=20, index_pairs=train_pairs)[0]\n", + "val_ot_dists = gw_mapped_ot_pairwise_parallel(cell_object_paths[anchor_ind], mapped_distbs, \n", + " num_processes=12, chunksize=20, index_pairs=val_pairs)[0]\n", + "test_ot_dists = gw_mapped_ot_pairwise_parallel(cell_object_paths[anchor_ind], mapped_distbs, \n", + " num_processes=12, chunksize=20, index_pairs=test_pairs)[0]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9ccd1918", + "metadata": {}, + "outputs": [], + "source": [ + "# Create PairedDataset objects for train/val/test sets for training dGW-OT model\n", + "train_data = PairedDataset(\n", + " image_dir = cell_image_path, \n", + " mapped_image_dir = mapped_cell_image_path,\n", + " distances = train_ot_dists.astype('float32'), \n", + " image_pairs = train_pairs,\n", + " transform = transforms.Compose([transforms.ToImage(),\n", + " transforms.ToDtype(torch.float32)]),\n", + ")\n", + "\n", + "val_data = PairedDataset(\n", + " image_dir = cell_image_path, \n", + " mapped_image_dir = mapped_cell_image_path,\n", + " distances = val_ot_dists.astype('float32'), \n", + " image_pairs = val_pairs,\n", + " transform = transforms.Compose([transforms.ToImage(),\n", + " transforms.ToDtype(torch.float32)]),\n", + ")\n", + "\n", + "test_data = PairedDataset(\n", + " image_dir = cell_image_path, \n", + " mapped_image_dir = mapped_cell_image_path,\n", + " distances = test_ot_dists.astype('float32'), \n", + " image_pairs = test_pairs,\n", + " transform = transforms.Compose([transforms.ToImage(),\n", + " transforms.ToDtype(torch.float32)]),\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "112f9ac4", + "metadata": {}, + "source": [ + "We initialize the dCellAligner-OT model and begin the two-stage training process. First, during pretraining, the model learns the Fused (Unbalanced) Gromov-Wasserstein mapping operation. More specifically, for each cell, the model learns to predict the subcellular protein distribution after mapping to the anchor cell.\n", + "\n", + "The dCellAligner-OT model pretraining took around 24 hours running on a Nvidia RTX 4500 Ada." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e95eaaf0", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/opt/conda/lib/python3.10/site-packages/torchvision/models/_utils.py:208: UserWarning: The parameter 'pretrained' is deprecated since 0.13 and may be removed in the future, please use 'weights' instead.\n", + " warnings.warn(\n", + "/opt/conda/lib/python3.10/site-packages/torchvision/models/_utils.py:223: UserWarning: Arguments other than a weight enum or `None` for 'weights' are deprecated since 0.13 and may be removed in the future. The current behavior is equivalent to passing `weights=EfficientNet_B4_Weights.IMAGENET1K_V1`. You can also use `weights=EfficientNet_B4_Weights.DEFAULT` to get the most up-to-date weights.\n", + " warnings.warn(msg)\n", + "Epoch 1/50: 100%|██████████| 2099/2099 [31:43<00:00, 1.10it/s, loss=0.319]\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Epoch 1/50, Loss: 0.395456\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 2/50: 100%|██████████| 2099/2099 [32:10<00:00, 1.09it/s, loss=0.21] \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Epoch 2/50, Loss: 0.321780\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 3/50: 100%|██████████| 2099/2099 [26:44<00:00, 1.31it/s, loss=0.203] \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Epoch 3/50, Loss: 0.287171\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 4/50: 100%|██████████| 2099/2099 [26:33<00:00, 1.32it/s, loss=0.0909]\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Epoch 4/50, Loss: 0.214656\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 5/50: 100%|██████████| 2099/2099 [25:30<00:00, 1.37it/s, loss=0.111] \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Epoch 5/50, Loss: 0.186027\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 6/50: 100%|██████████| 2099/2099 [25:34<00:00, 1.37it/s, loss=0.814] \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Epoch 6/50, Loss: 0.162329\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 7/50: 100%|██████████| 2099/2099 [25:44<00:00, 1.36it/s, loss=0.133] \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Epoch 7/50, Loss: 0.147380\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 8/50: 100%|██████████| 2099/2099 [25:36<00:00, 1.37it/s, loss=0.166] \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Epoch 8/50, Loss: 0.132804\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 9/50: 100%|██████████| 2099/2099 [25:40<00:00, 1.36it/s, loss=0.112] \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Epoch 9/50, Loss: 0.125499\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 10/50: 100%|██████████| 2099/2099 [25:41<00:00, 1.36it/s, loss=0.201] \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Epoch 10/50, Loss: 0.116555\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 11/50: 100%|██████████| 2099/2099 [25:15<00:00, 1.39it/s, loss=0.14] \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Epoch 11/50, Loss: 0.114798\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 12/50: 100%|██████████| 2099/2099 [25:30<00:00, 1.37it/s, loss=0.0448]\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Epoch 12/50, Loss: 0.107108\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 13/50: 100%|██████████| 2099/2099 [25:31<00:00, 1.37it/s, loss=0.0551]\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Epoch 13/50, Loss: 0.103031\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 14/50: 100%|██████████| 2099/2099 [25:38<00:00, 1.36it/s, loss=0.252] \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Epoch 14/50, Loss: 0.098223\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 15/50: 100%|██████████| 2099/2099 [26:35<00:00, 1.32it/s, loss=0.0921]\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Epoch 15/50, Loss: 0.094357\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 16/50: 100%|██████████| 2099/2099 [25:32<00:00, 1.37it/s, loss=0.134] \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Epoch 16/50, Loss: 0.093203\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 17/50: 100%|██████████| 2099/2099 [25:32<00:00, 1.37it/s, loss=0.0605]\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Epoch 17/50, Loss: 0.090122\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 18/50: 100%|██████████| 2099/2099 [25:29<00:00, 1.37it/s, loss=0.0553]\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Epoch 18/50, Loss: 0.086902\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 19/50: 100%|██████████| 2099/2099 [25:34<00:00, 1.37it/s, loss=0.0733]\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Epoch 19/50, Loss: 0.083662\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 20/50: 100%|██████████| 2099/2099 [25:40<00:00, 1.36it/s, loss=0.0487]\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Epoch 20/50, Loss: 0.082714\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 21/50: 100%|██████████| 2099/2099 [25:36<00:00, 1.37it/s, loss=0.104] \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Epoch 21/50, Loss: 0.081393\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 22/50: 100%|██████████| 2099/2099 [25:43<00:00, 1.36it/s, loss=0.0671]\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Epoch 22/50, Loss: 0.078136\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 23/50: 100%|██████████| 2099/2099 [25:38<00:00, 1.36it/s, loss=0.115] \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Epoch 23/50, Loss: 0.077529\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 24/50: 100%|██████████| 2099/2099 [25:29<00:00, 1.37it/s, loss=0.0573]\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Epoch 24/50, Loss: 0.077468\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 25/50: 100%|██████████| 2099/2099 [25:39<00:00, 1.36it/s, loss=0.0407]\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Epoch 25/50, Loss: 0.074328\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 26/50: 100%|██████████| 2099/2099 [25:49<00:00, 1.35it/s, loss=0.0787]\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Epoch 26/50, Loss: 0.071749\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 27/50: 100%|██████████| 2099/2099 [25:39<00:00, 1.36it/s, loss=0.0695]\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Epoch 27/50, Loss: 0.070086\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 28/50: 100%|██████████| 2099/2099 [31:35<00:00, 1.11it/s, loss=0.0704]\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Epoch 28/50, Loss: 0.068668\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 29/50: 100%|██████████| 2099/2099 [32:49<00:00, 1.07it/s, loss=0.0388]\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Epoch 29/50, Loss: 0.064706\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 30/50: 100%|██████████| 2099/2099 [32:25<00:00, 1.08it/s, loss=0.0637]\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Epoch 30/50, Loss: 0.063919\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 31/50: 100%|██████████| 2099/2099 [32:08<00:00, 1.09it/s, loss=0.0571]\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Epoch 31/50, Loss: 0.061969\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 32/50: 100%|██████████| 2099/2099 [32:03<00:00, 1.09it/s, loss=0.0344]\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Epoch 32/50, Loss: 0.062507\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 33/50: 100%|██████████| 2099/2099 [31:52<00:00, 1.10it/s, loss=0.0394]\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Epoch 33/50, Loss: 0.059359\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 34/50: 100%|██████████| 2099/2099 [32:10<00:00, 1.09it/s, loss=0.0489]\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Epoch 34/50, Loss: 0.058132\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 35/50: 100%|██████████| 2099/2099 [31:55<00:00, 1.10it/s, loss=0.0799]\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Epoch 35/50, Loss: 0.057911\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 36/50: 100%|██████████| 2099/2099 [32:38<00:00, 1.07it/s, loss=0.0397]\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Epoch 36/50, Loss: 0.056321\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 37/50: 100%|██████████| 2099/2099 [32:11<00:00, 1.09it/s, loss=0.0427]\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Epoch 37/50, Loss: 0.054310\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 38/50: 100%|██████████| 2099/2099 [28:48<00:00, 1.21it/s, loss=0.0631]\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Epoch 38/50, Loss: 0.056188\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 39/50: 100%|██████████| 2099/2099 [30:51<00:00, 1.13it/s, loss=0.0767] \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Epoch 39/50, Loss: 0.052495\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 40/50: 100%|██████████| 2099/2099 [30:20<00:00, 1.15it/s, loss=0.0486] \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Epoch 40/50, Loss: 0.050635\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 41/50: 100%|██████████| 2099/2099 [30:57<00:00, 1.13it/s, loss=0.0269]\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Epoch 41/50, Loss: 0.049588\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 42/50: 100%|██████████| 2099/2099 [27:57<00:00, 1.25it/s, loss=0.0557] \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Epoch 42/50, Loss: 0.050044\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 43/50: 100%|██████████| 2099/2099 [28:45<00:00, 1.22it/s, loss=0.0321]\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Epoch 43/50, Loss: 0.047658\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 44/50: 100%|██████████| 2099/2099 [32:27<00:00, 1.08it/s, loss=0.0464]\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Epoch 44/50, Loss: 0.047649\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 45/50: 100%|██████████| 2099/2099 [32:20<00:00, 1.08it/s, loss=0.0456]\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Epoch 45/50, Loss: 0.046697\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 46/50: 100%|██████████| 2099/2099 [33:05<00:00, 1.06it/s, loss=0.0484]\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Epoch 46/50, Loss: 0.045813\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 47/50: 100%|██████████| 2099/2099 [32:50<00:00, 1.06it/s, loss=0.06] \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Epoch 47/50, Loss: 0.046260\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 48/50: 100%|██████████| 2099/2099 [31:56<00:00, 1.10it/s, loss=0.031] \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Epoch 48/50, Loss: 0.044359\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 49/50: 100%|██████████| 2099/2099 [33:13<00:00, 1.05it/s, loss=0.06] \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Epoch 49/50, Loss: 0.043685\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epoch 50/50: 100%|██████████| 2099/2099 [44:40<00:00, 1.28s/it, loss=0.0741]\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Epoch 50/50, Loss: 0.044747\n" + ] + } + ], + "source": [ + "model = dCellAlignerNetwork(embedding_size=50, image_size=image_shape[0])\n", + "model = pretrain_model(train_data, model, dataset_name='sim_neuron', batch_size=8, epochs=50, lr=1e-3, \n", + " device='cuda', save_path='/path/to/save/pretrained/model/', return_model=True)" + ] + }, + { + "cell_type": "markdown", + "id": "bd50f6b6", + "metadata": {}, + "source": [ + "Next, during training, the model learns to extract features that preserve the CellAligner-OT distances between cells in the feature space, in addition to predicting the mapped protein distributions.\n", + "\n", + "The model is optimized with respect to two main loss components. The distance loss measures how well the the CellAligner-OT distances are preserved in the model's latent feature space, while the reconstruction loss measures accurately the model predicts the mapped protein distributions. The `dist_weight` parameter controls the relative weighting of these loss components during training. Ideally, the relatively contribution of both losses, which can be viewed by setting `show_loss_components = True`, should be around the same order of magnitude.\n", + "\n", + "To avoid overfitting, we apply L1 regularization (adjusted by `weight_decay`) and L2 regularization (adjusted by `sparsity_weight`, and `sparsity_target`). If the dGW-OT model is overfitting, you could experiment with increasing the `weight_decay` and `sparsity_weight`, or decreasing `sparsity_target`, to further regularize the model to resolve the issue.\n", + "\n", + "The dCellAligner-OT model training took around 48 hours running on a Nvidia RTX 4500 Ada." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6704bb14", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Training on device: cuda\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Loading pretrained weights from /home/jovyan/e/rkhu/Projects/CAJAL_spatial/data/package_dev/dgwote/models/pretrained_sim_neuron.pth\n", + "Starting training for 25 epochs...\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + " \r" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Epoch 1/25: Train Loss: 141.416748, Val Loss: 1.089571\n", + " → New best model saved (val_loss: 1.089571)\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + " \r" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Epoch 2/25: Train Loss: 0.840245, Val Loss: 0.695685\n", + " → New best model saved (val_loss: 0.695685)\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + " \r" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Epoch 3/25: Train Loss: 0.717471, Val Loss: 0.754261\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + " \r" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Epoch 4/25: Train Loss: 0.792152, Val Loss: 0.692320\n", + " → New best model saved (val_loss: 0.692320)\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + " \r" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Epoch 5/25: Train Loss: 0.745411, Val Loss: 0.646097\n", + " → New best model saved (val_loss: 0.646097)\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + " \r" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Epoch 6/25: Train Loss: 0.722791, Val Loss: 0.721418\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + " \r" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Epoch 7/25: Train Loss: 0.638340, Val Loss: 0.677450\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + " \r" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Epoch 8/25: Train Loss: 0.588748, Val Loss: 0.703082\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + " \r" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Epoch 9/25: Train Loss: 0.543514, Val Loss: 0.692757\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + " \r" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Epoch 10/25: Train Loss: 0.513111, Val Loss: 0.669878\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + " \r" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Epoch 11/25: Train Loss: 0.472981, Val Loss: 0.640772\n", + " → New best model saved (val_loss: 0.640772)\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + " \r" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Epoch 12/25: Train Loss: 0.449233, Val Loss: 0.549593\n", + " → New best model saved (val_loss: 0.549593)\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + " \r" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Epoch 13/25: Train Loss: 0.430085, Val Loss: 0.570916\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + " \r" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Epoch 14/25: Train Loss: 0.401759, Val Loss: 0.550535\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + " \r" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Epoch 15/25: Train Loss: 0.391115, Val Loss: 0.569693\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + " \r" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Epoch 16/25: Train Loss: 0.377396, Val Loss: 0.542184\n", + " → New best model saved (val_loss: 0.542184)\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + " \r" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Epoch 17/25: Train Loss: 0.361147, Val Loss: 0.554994\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + " \r" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Epoch 18/25: Train Loss: 0.347979, Val Loss: 0.631908\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + " \r" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Epoch 19/25: Train Loss: 0.335832, Val Loss: 0.513285\n", + " → New best model saved (val_loss: 0.513285)\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + " \r" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Epoch 20/25: Train Loss: 0.316852, Val Loss: 0.553770\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + " \r" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Epoch 21/25: Train Loss: 0.311076, Val Loss: 0.506876\n", + " → New best model saved (val_loss: 0.506876)\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + " \r" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Epoch 22/25: Train Loss: 0.297790, Val Loss: 0.498590\n", + " → New best model saved (val_loss: 0.498590)\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + " \r" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Epoch 23/25: Train Loss: 0.293798, Val Loss: 0.523204\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + " \r" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Epoch 24/25: Train Loss: 0.282792, Val Loss: 0.474511\n", + " → New best model saved (val_loss: 0.474511)\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + " \r" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Epoch 25/25: Train Loss: 0.274882, Val Loss: 0.466883\n", + " → New best model saved (val_loss: 0.466883)\n", + "\n", + "Evaluating on test set...\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/opt/conda/lib/python3.10/site-packages/cajal/subcellular_dl.py:1128: UserWarning: Using a target size (torch.Size([8])) that is different to the input size (torch.Size([8, 1])). This will likely lead to incorrect results due to broadcasting. Please ensure they have the same size.\n", + " with torch.no_grad():\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Final Test Loss: 10.465317\n", + "\n", + "Saving model...\n", + "Saved DWE model to /home/jovyan/e/rkhu/Projects/CAJAL_spatial/data/package_dev/dgwote/models//sim_neuron_final.pth\n" + ] + } + ], + "source": [ + "# Train the DGWOTE model with the prepared datasets\n", + "models, train_losses, val_losses = train_dCellAligner(\n", + " train_data, val_data, test_data,\n", + " save_path='/path/to/save/fully/trained/model/',\n", + " dataset_name='sim_neuron',\n", + " embedding_size=50, # 50-dimensional embeddings\n", + " image_shape=(256, 256), # Input image shape\n", + " device='cuda', # Use GPU if available\n", + " batch_size=8, # Batch size for training\n", + " epochs=25, # Number of epochs\n", + " learning_rate=0.001, # Adam learning rate\n", + " dist_weight=0.1, # Distance weight (vs reconstruction loss)\n", + " early_stopping=False, # Disable early stopping\n", + " weight_decay=1e-4, # L2 regularization weight\n", + " lr_gamma=0.95, # Learning rate decay factor\n", + " sparsity_weight=1e-3, # Sparsity weight for the embedding loss\n", + " sparsity_target=0.1, # Target sparsity for the embedding loss\n", + " pretrained_path=\"/path/to/save/pretrained/model/sim_neuron_pretrained_best.pth\"\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "5a5a11d0", + "metadata": {}, + "source": [ + "The `train_dCellAligner` function saves two versions of the model, the best performing model based on performance on validation dataset (`_best.pth`), and the final model after all training epochs (`_final.pth`). Here, we load the best model based on validation loss." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cff2c062", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/opt/conda/lib/python3.10/site-packages/torchvision/models/_utils.py:208: UserWarning: The parameter 'pretrained' is deprecated since 0.13 and may be removed in the future, please use 'weights' instead.\n", + " warnings.warn(\n", + "/opt/conda/lib/python3.10/site-packages/torchvision/models/_utils.py:223: UserWarning: Arguments other than a weight enum or `None` for 'weights' are deprecated since 0.13 and may be removed in the future. The current behavior is equivalent to passing `weights=EfficientNet_B4_Weights.IMAGENET1K_V1`. You can also use `weights=EfficientNet_B4_Weights.DEFAULT` to get the most up-to-date weights.\n", + " warnings.warn(msg)\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Loaded dGWOT model from /home/jovyan/e/rkhu/Projects/CAJAL_spatial/data/package_dev/dgwote/models/sim_neuron_best.pth\n", + "Config: {'input_channels': 3, 'embedding_size': 50, 'image_size': 256}\n" + ] + } + ], + "source": [ + "# load best model\n", + "model = load_dCellAligner_model('/path/to/save/fully/trained/model/sim_neuron_best.pth')" + ] + }, + { + "cell_type": "markdown", + "id": "fefc2421", + "metadata": {}, + "source": [ + "To evaluate model performance, we can look at how well the true CellAligner-OT distances are preserved in the dCellAligner-OT feature space. We can also look at how well the model predicted the mapped protein distribution." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f7cf85e3", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Extracting embeddings for 1670 unique images...\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Extracting embeddings: 0%| | 0/27 [00:00" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plot_distance_predictions(model, test_data)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c8fcfd80", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAABb4AAAJSCAYAAAAMOtMPAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjYsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvq6yFwwAAAAlwSFlzAAAPYQAAD2EBqD+naQAA0b9JREFUeJzs3XmcHHW1///3p5ZeZstuEpZAgLBFFgHDYrgBLoKyiCigIEsEQRYX+IHKVRBFQVHky/2qoNzrjX4R7mURAa+iBkRB2TdFQNmXkH2ZfXqpqvP7o7o7M5mZZBKSTGfyej4eA5nq6u7qYXKoOnU+5zgzMwEAAAAAAAAAMEJ4w30AAAAAAAAAAACsTyS+AQAAAAAAAAAjColvAAAAAAAAAMCIQuIbAAAAAAAAADCikPgGAAAAAAAAAIwoJL4BAAAAAAAAACMKiW8AAAAAAAAAwIhC4hsAAAAAAAAAMKKQ+AYAAAAAAAAAjCgkvtejr33ta3LOrdNzf/rTn8o5p9dff339HlQvr7/+upxz+ulPf7rB3mN1Bvr5bLvttpo9e/awHA8A4taaELeA4UWMWj1iFDC8iFGrR4wCNh7i0eoRjzZfJL4lPffcczr55JO15ZZbKpvNaostttAnPvEJPffcc8N9aMNq0aJFuuiii7TzzjuroaFBjY2N2nvvvfXNb35Tra2tG/VYbrnlFp188smaNm2anHM66KCDBt23WCzqS1/6krbYYgvl83ntu+++mjt3br/9yuWyvv71r2u77bZTNpvVdtttp29+85uKoqjPfo8//rg+85nPaPr06WpsbNSUKVN0wgkn6MUXX+z3mo899pjOPfdc7b333grDcJ3/xwOsCXFrYPUUtySpo6NDX/ziFzV16lRls1ltueWWOu6449Td3V3bZ8GCBbr44ot18MEHq7m5Wc45/fGPfxz0NR966CHNnDlTDQ0NmjRpkj73uc+ps7Oz335DjYXAhkCMGlg9xajOzk6df/752mqrrZTNZrXLLrvo+uuv77ffQQcdJOfcgF9hGPbZ94ILLtBee+2lsWPHqqGhQbvssou+9rWvDRijnnzySX3gAx9QS0uLmpubddhhh+mZZ57ZUB8X6IMYNbB6ilG9vfLKK8rlcnLO6Yknnuj3eGtrq8466yxNmDBBjY2NOvjgg/XUU0/1269QKOhb3/qWdt11VzU0NGjLLbfU8ccf3++/ezUJONDXwoULN9jnxOaJeDSweopHa5OP6u2KK66Qc07vfve7B3y8VCrpyiuv1M4776xcLqeJEyfqyCOP1Lx582r7dHZ26rLLLtMHPvABjR07do03D5Ik0fXXX68999xT+Xxe48aN0yGHHKK//vWva/ORR5RguA9guN1xxx068cQTNXbsWJ1xxhmaOnWqXn/9df3kJz/R7bffrv/5n//RscceO6TXuuSSS3TxxRev03Gccsop+vjHP65sNrtOz1/fHn/8cR1xxBHq7OzUySefrL333luS9MQTT+jb3/62HnjgAf3+97/faMdz/fXX68knn9R73/teLVu2bLX7zp49W7fffrvOP/98TZs2TT/96U91xBFH6P7779fMmTNr+5188sm67bbbdPrpp2ufffbRI488oksvvVRvvvmmbrjhhtp+V111lf7yl7/o+OOP1+67766FCxfqBz/4gfbaay898sgjfYLYb37zG/3nf/6ndt99d2233XYDJseBd4q4NbB6i1ttbW2aNWuW5s2bp7POOks77LCDlixZogcffFDFYlENDQ2SpH/+85+66qqrNG3aNO222256+OGHB33NZ555Rv/6r/+qXXbZRddcc43mzZunq6++Wi+99JLuueeePvsONRYC6xsxamD1FKPiONbhhx+uJ554Quedd56mTZum3/3udzr33HO1YsUKffnLX67t+5WvfEWf+tSn+jy/q6tLZ599tg477LA+2x9//HEdeOCB+uQnP6lcLqenn35a3/72t3XvvffqgQcekOelNTdPPfWUZs6cqa233lqXXXaZkiTRddddp1mzZumxxx7TTjvttOF/CNhsEaMGVk8xalUXXHCBgiBQsVjs91iSJDryyCP117/+VV/4whc0fvx4XXfddTrooIP05JNPatq0abV9P/GJT+juu+/WmWeeqb322kvz58/XD3/4Q+2///569tlntc022/R57csvv1xTp07ts2306NEb5DNi80Q8Gli9xaO1yUdVzZs3T1deeaUaGxsHfLxcLuvII4/UQw89pDPPPFO77767VqxYoUcffVRtbW3aaqutJElLly7V5ZdfrilTpmiPPfZYbYGUJJ1++um66aabdOqpp+ozn/mMurq69PTTT2vx4sVr9ZlHFNuMvfzyy9bQ0GA777yzLV68uM9jS5YssZ133tkaGxvtlVdeWe3rdHZ2bsjDXG9ee+01k2Rz5sxZ7X4rVqywLbfc0iZOnGgvvPBCv8cXLlxo3/jGN9b6/S+77DJb9Vdum222sdNOO22Nz33zzTctjmMzM5s+fbrNmjVrwP0effRRk2Tf/e53a9t6enps++23t/3337+27bHHHjNJdumll/Z5/oUXXmjOOfvrX/9a2/aXv/zFisVin/1efPFFy2az9olPfKLP9oULF1p3d7eZmZ133nn9Pi/wThG3BlaPceucc86x0aNH26uvvrra/drb223ZsmVmZnbbbbeZJLv//vsH3PeDH/ygTZ482dra2mrb/uM//sMk2e9+97vatqHGQmB9I0YNrN5i1K233mqS7Cc/+Umf7R/96Ectl8vZokWLVvv8G2+80STZTTfdtMZjvPrqq02SPfzww7VtRxxxhI0ZM8aWLl1a2zZ//nxramqyj3zkI2t8TWBdEaMGVm8xqrff/va3lslk7JJLLjFJ9vjjj/d5/JZbbjFJdtttt9W2LV682EaPHm0nnnhibdu8efNMkl100UV9nv+HP/zBJNk111xT2zZnzpwB3wtYn4hHA6vHeDTUfFRvH/vYx+yQQw6xWbNm2fTp0/s9ftVVV1kYhvboo4+u9nUKhYItWLDAzMwef/zx1f4Mq/HwjjvuWOPxbU4261Yn3/3ud9Xd3a0bbrhBEyZM6PPY+PHj9eMf/1hdXV36zne+U9te7Qv0/PPP66STTtKYMWNqlXMD9Qzq6enR5z73OY0fP17Nzc360Ic+pLffflvOOX3ta1+r7TdQT6Vtt91WRx11lP785z9rxowZyuVy2m677fT//t//6/Mey5cv10UXXaTddttNTU1Namlp0Qc/+MF1Xsrw4x//WG+//bauueYa7bzzzv0enzhxoi655JI+2+655x4deOCBamxsVHNzs4488sj1ujRn6623rlUJrc7tt98u3/d11lln1bblcjmdccYZevjhh/XWW29Jkh588EFJ0sc//vE+z//4xz8uM9Mtt9xS23bAAQcok8n02W/atGmaPn26XnjhhT7bJ06cqHw+v3YfDlgLxK2B1Vvcam1t1Zw5c3TWWWdp6tSpKpVKA1YpSVJzc7PGjh27xtdsb2/X3LlzdfLJJ6ulpaW2/dRTT1VTU5NuvfXW2rahxkJgfSNGDazeYtTqzoMKhYLuuuuu1T7/5ptvVmNjo4455pg1vte2224rSX2WJT/44IM69NBDNW7cuNq2yZMna9asWfrf//3fAVujAOsDMWpg9Rajqsrlsj7/+c/r85//vLbffvsB97n99ts1ceJEfeQjH6ltmzBhgk444QTdddddtfOvjo6O2mfpbfLkyZI06DVcR0eH4jh+x58FWBXxaGD1GI+Gmo+qeuCBB3T77bfr2muvHfDxJEn07//+7zr22GM1Y8YMRVHUpxVmb9lsVpMmTRrS+15zzTWaMWOGjj32WCVJoq6uriEf80i2WSe+f/WrX2nbbbfVgQceOODj//Iv/6Jtt91Wv/71r/s9dvzxx6u7u1tXXnmlzjzzzEHfY/bs2fr+97+vI444QldddZXy+byOPPLIIR/jyy+/rOOOO07vf//79b3vfU9jxozR7Nmz+/wlfvXVV3XnnXfqqKOO0jXXXKMvfOELevbZZzVr1izNnz9/yO9Vdffddyufz+u4444b0v433nijjjzySDU1Nemqq67SpZdequeff14zZ87coMMRBvL0009rxx137JMUkqQZM2ZIUq13ZPUEaNUTnGrrgSeffHK172NmWrRokcaPH78+DhsYMuLWwOotbv35z39WoVDQDjvsoOOOO04NDQ3K5/N63/vet849bJ999llFUaR99tmnz/ZMJqM999xTTz/9dG3bUGMhsL4RowZWbzGqWCzK9/1+N/aHch60ZMkSzZ07Vx/+8IcHXL4bRZGWLl2q+fPn6/e//70uueQSNTc31+JP9f0HSjI1NDSoVCrp73//+7p+NGC1iFEDq7cYVXXttddqxYoV/ZJcvT399NPaa6+9+iWlZsyYoe7u7lrrye23315bbbWVvve97+lXv/qV5s2bp8cee0xnn322pk6d2u9GoCQdfPDBamlpUUNDgz70oQ/ppZdeWm+fDSAeDaxe49FQxXGsz372s/rUpz6l3XbbbcB9nn/+ec2fP1+77767zjrrLDU2NqqxsVG777677r///nV63/b2dj322GN673vfqy9/+csaNWqUmpqatN122/UpkNosDXfJ+XBpbW01SXbMMcesdr8PfehDJsna29vNbOXyiN7LpqpWXTrx5JNPmiQ7//zz++w3e/Zsk2SXXXZZbVt1OdVrr71W27bNNtuYJHvggQdq2xYvXmzZbNYuvPDC2rZCoVBbdlH12muvWTabtcsvv7zPNg1hacmYMWNsjz32WO0+VR0dHTZ69Gg788wz+2xfuHChjRo1qs/29bHUzWz1S0umT59uhxxySL/tzz33nEmyH/3oR2Zm9otf/MIk2Y033thnvx/96Ecmyd797nev9hiqS3xXXSLcG61OsL4RtwZXb3HrmmuuMUk2btw4mzFjht1000123XXX2cSJE23MmDE2f/78AZ+3ulYn1cd6/2yrjj/+eJs0aVLt+6HGQmB9IkYNrt5i1Pe+9z2TZA8++GCf7RdffLFJsqOOOmrQ537/+983Sfab3/xmwMcffvhhk1T72mmnnfrFtN1228123HFHi6Kotq1YLNqUKVNMkt1+++2rPX5gXRCjBldvMcrMbMGCBdbc3Gw//vGPzWzw9iONjY12+umn93v+r3/9a5Nkv/3tb2vbHn30Udt+++37xKi999671kag6pZbbrHZs2fbz372M/vlL39pl1xyiTU0NNj48ePtzTffXOOxA2tCPBpcPcaj3tbU6uQHP/iBjRo1qta+ZqBWJ3fccUftWnHatGk2Z84cmzNnjk2bNs0ymUyf1ru9ra7VyVNPPVV7zYkTJ9p1111nN910k82YMcOcc3bPPfes1eccSTbbiu/qUqfm5ubV7ld9vL29vc/2s88+e43v8dvf/laSdO655/bZ/tnPfnbIx7nrrrv2uQM4YcIE7bTTTnr11Vdr27LZbO0OdxzHWrZsmZqamrTTTjsNOM16Tdrb29f4c6maO3euWltbdeKJJ2rp0qW1L9/3te+++67z3ap11dPTM+BAhlwuV3tcko444ghts802uuiii3THHXfojTfe0K233qqvfOUrCoKgtt9A/vGPf+i8887T/vvvr9NOO23DfBBgAMStwdVb3Kou03fO6b777tNJJ52kc845R3feeadWrFihH/7wh2v9mtW4NFiM6x23hhoLgfWJGDW4eotRJ510kkaNGqXTTz9dc+fO1euvv64bbrhB1113naTVx4ibb75ZEyZM0Pvf//4BH9911101d+5c3XnnnfriF7+oxsbGfq1Lzj33XL344os644wz9Pzzz+vvf/+7Tj31VC1YsGCN7w+sK2LU4OotRknSl770JW233Xb9huuuam3OecaMGaM999xTF198se68805dffXVev3113X88cerUCjU9jvhhBM0Z84cnXrqqfrwhz+sb3zjG/rd736nZcuW6Yorrlgvnw+bN+LR4OoxHg3VsmXL9NWvflWXXnppv/Y1vVXPizo6OnTfffdp9uzZmj17tu69916ZWZ/2NkNVfc1ly5bprrvu0jnnnKOTTjpJ9913n8aNG6dvfvOb6/ahRoBguA9guFT/IlUDzmAGC0irTnceyBtvvCHP8/rtu8MOOwz5OKdMmdJv25gxY7RixYra99X+QNddd51ee+21Pj3IevdOHKqWlpY1/lyqqsu9DjnkkEFfa2PK5/MD9tGtnshUl9Xmcjn9+te/1gknnKCPfvSjktKA/Z3vfEdXXHGFmpqaBnz9hQsX6sgjj9SoUaNqPXSBjYW4Nbh6i1vVWHP00Uf3iSf77befpk6dqoceemidX3OwGNe7bcBQYyGwPhGjBldvMWrSpEm6++67dcopp+iwww6rve73v/99nXbaaYOeB7366qt6+OGH9ZnPfEZBMPBlREtLiw499FBJ0jHHHKObb75ZxxxzjJ566intsccektIL9rfeekvf/e539bOf/UyStM8+++iLX/zias/DgHeCGDW4eotRjzzyiG688Ubdd999a+yrO9Rznra2Nh144IH6whe+oAsvvLC23z777KODDjpIc+bM0TnnnDPo+8ycOVP77ruv7r333nX5SEAfxKPB1Vs8WhuXXHKJxo4du8abC9W49L73vU9bb711bfuUKVM0c+bMd3StOHXqVO2777617U1NTTr66KP185//XFEUDXr+NpJtfp+4YtSoUZo8ebL+9re/rXa/v/3tb9pyyy37/YXZWEmDwRKrZlb785VXXqlLL71Up59+ur7xjW9o7Nix8jxP559/vpIkWev33HnnnfXMM8+oVCr16/24qurr33jjjQM23N/Yf6kmT56st99+u9/2agXRFltsUds2ffp0/f3vf9fzzz+vFStWaNddd1U+n9cFF1ygWbNm9XuNtrY2ffCDH1Rra6sefPDBPq8FbAzErcHVW9yqxodVByhJ0rve9a4+J4tDVR2+VI1nvS1YsKBPTFqbWAisL8SowdVbjJLS3qGvvvqqnn32WXV1dWmPPfao9eLccccdB3zOzTffLEn6xCc+MeT3+chHPqJTTjlF//M//1NLfEvSFVdcoYsuukjPPfecRo0apd12201f/vKXV/v+wDtBjBpcvcWoL37xizrwwAM1derUWo/epUuXSkrPZd58881aQm7y5MmDnhtJK895fvGLX2jRokX60Ic+1Ge/WbNmqaWlRX/5y19Wm/iW0gF3//znP9/RZwMk4tHq1Fs8GqqXXnpJN9xwg6699to+vc0LhYLK5bJef/11tbS0aOzYsWu8Vuw9u2mo1vSa5XJZXV1dGjVq1Fq/9qZus018S9JRRx2l//iP/9Cf//zn2iTc3h588EG9/vrr+vSnP71Or7/NNtsoSRK99tprmjZtWm37yy+/vM7HPJDbb79dBx98sH7yk5/02d7a2rpOwxePPvpoPfzww/rFL36hE088cbX7Vqdrv+td76pV9wynPffcU/fff7/a29v7/M/h0UcfrT3em3NO06dPr33/m9/8RkmS9PsshUJBRx99tF588UXde++92nXXXTfchwBWg7g1sHqLW3vvvbckDZh8nj9//oATytfk3e9+t4Ig0BNPPKETTjihtr1UKumZZ57ps21tYyGwvhCjBlZvMarK9/0+8aBayTjY+958883afvvttd9++w35PYrFopIkUVtbW7/HxowZ0+f35N5779VWW221TjESGApi1MDqLUa9+eabeuONNwasav3Qhz6kUaNGqbW1VVJ6TvPggw8qSZI+1eGPPvqoGhoaajfSFi1aJEl9qlGlNIEXx7GiKFrjcb366qurbV8ArA3i0cDqLR4N1dtvv60kSfS5z31On/vc5/o9PnXqVH3+85/Xtddeq912201hGA56rbgucWaLLbbQpEmTBn3NXC435BYyI81m2+Nbkr7whS8on8/r05/+tJYtW9bnseXLl+vss89WQ0ODvvCFL6zT6x9++OGSVOuXWPX9739/3Q54EL7v97njJkm33XbbgL/wQ3H22Wdr8uTJuvDCC2tTsHtbvHhxrT/Q4YcfrpaWFl155ZUql8v99l2yZMk6HcO6Ou644xTHsW644YbatmKxqDlz5mjffffts4xkVT09Pbr00ks1efLkPgE2jmN97GMf08MPP6zbbrtN+++//wb9DMDqELcGVm9xa6eddtIee+yhu+66q1ahJEm///3v9dZbbw3aG3d1Ro0apUMPPVQ///nP+yz/u/HGG9XZ2anjjz++tu2dxELgnSBGDazeYtRAlixZoquuukq77777gBePTz/9tF544QWddNJJAz6/tbV1wOP9z//8T0lpO4HVueWWW/T444/r/PPPX2NrA2BdEaMGVm8x6oYbbtAvf/nLPl/V1gFXX321brrpptq+xx13nBYtWqQ77rijtm3p0qW67bbbdPTRR9f6f1cT4P/zP//T573uvvtudXV16T3vec9qP8NvfvMbPfnkk/rABz7wjj8fIBGPBlNv8Wio3v3ud/eLW7/85S81ffp0TZkyRb/85S91xhlnSEpb1xxxxBF66KGH9I9//KP2Gi+88IIeeuihdbpWlKSPfexjeuuttzR37tzatqVLl+quu+7SIYccstmeX23WFd/Tpk3Tz372M33iE5/QbrvtpjPOOKO2nOonP/mJli5dqv/+7/+u3UVaW3vvvbc++tGP6tprr9WyZcu033776U9/+lPtL69zbr18jqOOOkqXX365PvnJT+qAAw7Qs88+q5tuuknbbbfdOr3emDFj9Mtf/lJHHHGE9txzT5188sm16sWnnnpK//3f/11L/ra0tOj666/XKaecor322ksf//jHNWHCBL355pv69a9/rfe97336wQ9+8I4/4wMPPKAHHnhAUhq8urq6asHuX/7lX/Qv//IvkqR9991Xxx9/vP7t3/5Nixcv1g477KCf/exntf+mvZ1wwgnaYosttOuuu6q9vV3/9V//pVdffVW//vWv+9wJu/DCC3X33Xfr6KOP1vLly/Xzn/+8z+ucfPLJtT+/8cYbuvHGGyVJTzzxhCTVjnObbbbRKaec8o5/Fti8EbcGVo9x6//8n/+j97///Zo5c6Y+/elPq62tTddcc4123HHHfktpq3Hiueeek5Qms//85z9LSnvFVV1xxRU64IADNGvWLJ111lmaN2+evve97+mwww7rcyG2NrEQWJ+IUQOrxxg1a9Ys7b///tphhx20cOFC3XDDDers7NT//u//DnhhVE00Ddbm5I9//KM+97nP6bjjjtO0adNUKpX04IMP6o477tA+++zT53zpgQce0OWXX67DDjtM48aN0yOPPKI5c+boAx/4gD7/+c+/488GDIYYNbB6i1HV2QO9VSu8Z82a1edG2nHHHaf99ttPn/zkJ/X8889r/Pjxuu666xTHsb7+9a/X9jv66KM1ffp0XX755XrjjTe033776eWXX9YPfvADTZ48uZaQkqQDDjhA73nPe7TPPvto1KhReuqpp/Rf//Vf2nrrrWstmYB3ing0sHqLR9LQ8lHjx4/Xhz/84X7PvfbaayWp32NXXnml7rvvPh1yyCG1CvH/+3//r8aOHdsvzvzgBz9Qa2trrYXKr371K82bN09SOqy02r7k3/7t33Trrbfqox/9qP6//+//06hRo/SjH/1I5XJZV1555Tv+OWyyDPa3v/3NTjzxRJs8ebKFYWiTJk2yE0880Z599tl++1522WUmyZYsWTLoY711dXXZeeedZ2PHjrWmpib78Ic/bP/85z9Nkn3729+u7TdnzhyTZK+99lpt2zbbbGNHHnlkv/eZNWuWzZo1q/Z9oVCwCy+80CZPnmz5fN7e97732cMPP9xvv9dee80k2Zw5c4b0c5k/f75dcMEFtuOOO1oul7OGhgbbe++97YorrrC2trY++95///12+OGH26hRoyyXy9n2229vs2fPtieeeGK1P59tttnGTjvttDUeS/W5A31ddtllffbt6emxiy66yCZNmmTZbNbe+9732m9/+9t+r3nVVVfZzjvvbLlczsaMGWMf+tCH7Omnn+6336xZswZ971U/z/333z/ofr3/WwDvFHFrYPUUt8zM5s6da/vtt5/lcjkbO3asnXLKKbZgwYJ++w01xpiZPfjgg3bAAQdYLpezCRMm2HnnnWft7e399htqLAQ2BGLUwOopRl1wwQW23XbbWTabtQkTJthJJ51kr7zyyoD7xnFsW265pe21116Dvt7LL79sp556qm233XaWz+ctl8vZ9OnT7bLLLrPOzs5++x522GE2fvx4y2aztvPOO9u3vvUtKxaLazxuYH0gRg2snmLUqqo/r8cff7zfY8uXL7czzjjDxo0bZw0NDTZr1qxB96t+vmw2a+PHj7ePf/zj9uqrr/bZ7ytf+YrtueeeNmrUKAvD0KZMmWLnnHOOLVy4cK2PG1gT4tHA6ikerU0+alWzZs2y6dOnD/jYk08+aYceeqg1NjZac3OzHXPMMfbiiy/222+bbbYZ9P17/zczM3vllVfs2GOPtZaWFsvn83bIIYfYY489tsbPOJI5s1XWJGCDe+aZZ/Se97xHP//5z9dqOBAADBfiFoB6RowCUM+IUQDqBfEIm5vNs8HLRtTT09Nv27XXXivP82rtOQCgnhC3ANQzYhSAekaMAlAviEfAZt7je2P4zne+oyeffFIHH3ywgiDQPffco3vuuUdnnXUWw8UA1CXiFoB6RowCUM+IUQDqBfEIkGh1soHNnTtXX//61/X888+rs7NTU6ZM0Smn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AAAwopD4BgAAAAAAAACMKCS+AQAAAAAAAAAjColvAAAAAAAAAMCIQuIbG8Trr78u55x++tOfDsv7f+1rX5Nzrs+2bbfdVrNnzx6W4wFQ/4hbAOoZMQpAPSNGAagXxCP0VleJ75/+9KdyztW+giDQlltuqdmzZ+vtt98e7sNb76677rph+4tYT8cgSYsWLdJFF12knXfeWQ0NDWpsbNTee++tb37zm2ptbd2ox3LLLbfo5JNP1rRp0+Sc00EHHTTovsViUV/60pe0xRZbKJ/Pa99999XcuXP77Vcul/X1r39d2223nbLZrLbbbjt985vfVBRFffZ7/PHH9ZnPfEbTp09XY2OjpkyZohNOOEEvvvhiv9d87LHHdO6552rvvfdWGIb9Ais2DuLW5nkMUn3FLUnq6OjQF7/4RU2dOlXZbFZbbrmljjvuOHV3d9f2WbBggS6++GIdfPDBam5ulnNOf/zjHwd9zYceekgzZ85UQ0ODJk2apM997nPq7Ozst99QYyE2PmLU5nkMUn3FqM7OTp1//vnaaqutlM1mtcsuu+j666/vt99BBx3U5/e191cYhn32veCCC7TXXntp7Nixamho0C677KKvfe1rA8aoJ598Uh/4wAfU0tKi5uZmHXbYYXrmmWc21MfFWiBGbZ7HINVXjOrtlVdeUS6Xk3NOTzzxRL/HW1tbddZZZ2nChAlqbGzUwQcfrKeeeqrffoVCQd/61re06667qqGhQVtuuaWOP/54Pffcc332W/XvQO+vhQsXbrDPif6IR5vnMUj1FY/WJh/V2xVXXCHnnN797ncP+HipVNKVV16pnXfeWblcThMnTtSRRx6pefPm1fbp7OzUZZddpg984AMaO3bsGm8eJEmi66+/Xnvuuafy+bzGjRunQw45RH/961/X5iNvcMFwH8BALr/8ck2dOlWFQkGPPPKIfvrTn+rPf/6z/v73vyuXyw334a031113ncaPHz+sd33q4Rgef/xxHXHEEers7NTJJ5+svffeW5L0xBNP6Nvf/rYeeOAB/f73v99ox3P99dfrySef1Hvf+14tW7ZstfvOnj1bt99+u84//3xNmzZNP/3pT3XEEUfo/vvv18yZM2v7nXzyybrtttt0+umna5999tEjjzyiSy+9VG+++aZuuOGG2n5XXXWV/vKXv+j444/X7rvvroULF+oHP/iB9tprLz3yyCN9gthvfvMb/ed//qd23313bbfddgMmx7HxELc2r2Oot7jV1tamWbNmad68eTrrrLO0ww47aMmSJXrwwQdVLBbV0NAgSfrnP/+pq666StOmTdNuu+2mhx9+eNDXfOaZZ/Sv//qv2mWXXXTNNddo3rx5uvrqq/XSSy/pnnvu6bPvUGMhhg8xavM6hnqKUXEc6/DDD9cTTzyh8847T9OmTdPvfvc7nXvuuVqxYoW+/OUv1/b9yle+ok996lN9nt/V1aWzzz5bhx12WJ/tjz/+uA488EB98pOfVC6X09NPP61vf/vbuvfee/XAAw/I89L6nqeeekozZ87U1ltvrcsuu0xJkui6667TrFmz9Nhjj2mnnXba8D8ErBExavM6hnqKUau64IILFASBisViv8eSJNGRRx6pv/71r/rCF76g8ePH67rrrtNBBx2kJ598UtOmTavt+4lPfEJ33323zjzzTO21116aP3++fvjDH2r//ffXs88+q2222abPa1f/DvQ2evToDfIZsXrEo83rGOotHq1NPqpq3rx5uvLKK9XY2Djg4+VyWUceeaQeeughnXnmmdp99921YsUKPfroo2pra9NWW20lSVq6dKkuv/xyTZkyRXvsscdqC6Qk6fTTT9dNN92kU089VZ/5zGfU1dWlp59+WosXL16rz7zBWR2ZM2eOSbLHH3+8z/YvfelLJsluueWWYTqyDWP69Ok2a9asIe3b2dk57MewNl577TWTZHPmzFntfitWrLAtt9zSJk6caC+88EK/xxcuXGjf+MY31vr9L7vsMlv113ubbbax0047bY3PffPNNy2OYzNb/c/n0UcfNUn23e9+t7atp6fHtt9+e9t///1r2x577DGTZJdeemmf51944YXmnLO//vWvtW1/+ctfrFgs9tnvxRdftGw2a5/4xCf6bF+4cKF1d3ebmdl5553X7/Ni4yBuDY64tXbeSdw655xzbPTo0fbqq6+udr/29nZbtmyZmZnddtttJsnuv//+Aff94Ac/aJMnT7a2trbatv/4j/8wSfa73/2utm2osRDDgxg1OGLU2lnXGHXrrbeaJPvJT37SZ/tHP/pRy+VytmjRotU+/8YbbzRJdtNNN63xGK+++mqTZA8//HBt2xFHHGFjxoyxpUuX1rbNnz/fmpqa7CMf+cgaXxMbFjFqcMSotfNOzqOqfvvb31omk7FLLrlkwN/LW265xSTZbbfdVtu2ePFiGz16tJ144om1bfPmzTNJdtFFF/V5/h/+8AeTZNdcc01t22B/B7DxEY8GRzxaOxsjH9Xbxz72MTvkkENs1qxZNn369H6PX3XVVRaGoT366KOrfZ1CoWALFiwwM7PHH398tT/Dajy844471nh8w62uWp0M5sADD5SULjvq7R//+IeOO+44jR07VrlcTvvss4/uvvvufs9vbW3VBRdcoG233VbZbFZbbbWVTj31VC1durS2z+LFi3XGGWdo4sSJyuVy2mOPPfSzn/2sz+tU+wRdffXVuuGGG7T99tsrm83qve99rx5//PE++y5cuFCf/OQna0s6J0+erGOOOUavv/66pLS/z3PPPac//elPtaU01SUM1SU2f/rTn3TuuefqXe96V+0OzOzZs7Xtttv2+4wD9RCSpJ///OeaMWOGGhoaNGbMGP3Lv/xL7W7V6o6h+nM7//zztfXWWyubzWqHHXbQVVddpSRJ+v18Z8+erVGjRmn06NE67bTThrwc5Mc//rHefvttXXPNNdp55537PT5x4kRdcsklfbbdc889OvDAA9XY2Kjm5mYdeeSR/ZaMvRNbb711rUpodW6//Xb5vq+zzjqrti2Xy+mMM87Qww8/rLfeekuS9OCDD0qSPv7xj/d5/sc//nGZmW655ZbatgMOOECZTKbPftOmTdP06dP1wgsv9Nk+ceJE5fP5tftw2GiIW8St3jZk3GptbdWcOXN01llnaerUqSqVSgNWKUlSc3Ozxo4du8bXbG9v19y5c3XyySerpaWltv3UU09VU1OTbr311tq2ocZC1BdiFDGqtw0Zo1Z3HlQoFHTXXXet9vk333yzGhsbdcwxx6zxvar/HXv/rB588EEdeuihGjduXG3b5MmTNWvWLP3v//7vgK1RMPyIUcSo3jb09Z+UVkR+/vOf1+c//3ltv/32A+5z++23a+LEifrIRz5S2zZhwgSdcMIJuuuuu2rnXx0dHbXP0tvkyZMladBruI6ODsVx/I4/C9Yv4hHxqLd6yUdVPfDAA7r99tt17bXXDvh4kiT693//dx177LGaMWOGoijq0wqzt2w2q0mTJg3pfa+55hrNmDFDxx57rJIkUVdX15CPeWOry1Ynq6r+5RwzZkxt23PPPaf3ve992nLLLXXxxRersbFRt956qz784Q/rF7/4hY499lhJaY+aAw88UC+88IJOP/107bXXXlq6dKnuvvtuzZs3T+PHj1dPT48OOuggvfzyy/rMZz6jqVOn6rbbbtPs2bPV2tqqz3/+832O5+abb1ZHR4c+/elPyzmn73znO/rIRz6iV199tdZ/8KMf/aiee+45ffazn9W2226rxYsXa+7cuXrzzTe17bbb6tprr9VnP/tZNTU16Stf+Yqk/v9jPPfcczVhwgR99atfXadfoq9//ev62te+pgMOOECXX365MpmMHn30Uf3hD3/QYYcdttpj6O7u1qxZs/T222/r05/+tKZMmaKHHnpI//Zv/6YFCxbU/lKZmY455hj9+c9/1tlnn61ddtlFv/zlL3XaaacN6Rjvvvtu5fN5HXfccUPa/8Ybb9Rpp52mww8/XFdddZW6u7t1/fXXa+bMmXr66acHDMIbytNPP60dd9yxT1JIkmbMmCEpbROw9dZb106AVj3BqbYeePLJJ1f7PmamRYsWafr06evr0LERELeIW1UbOm79+c9/VqFQ0A477KDjjjtOd955p5Ik0f77768f/vCH2nPPPdf6NZ999llFUaR99tmnz/ZMJqM999xTTz/9dG3bUGMh6gsxihhVtaFjVLFYlO/7/W7s9z4POvPMMwd87pIlSzR37lx97GMfG3D5bhRFam1tValU0t///nddcsklam5ursWf6vsPlGRqaGioPW+//fZ7Jx8RGwAxihhVtbGu/6699lqtWLFCl1xyie64444B93n66ae111579UtKzZgxQzfccINefPFF7bbbbtp+++211VZb6Xvf+5522mknvec979H8+fNrs1hWvREoSQcffLA6OzuVyWR0+OGH63vf+16f1ikYPsQj4lFVPeWjpLSd3Gc/+1l96lOf0m677TbgPs8//7zmz5+v3XffXWeddZZ+9rOfqVQqabfddtO///u/6+CDD17r921vb6/NnPvyl7+s73//++rs7NTUqVP17W9/WyeccMI7/Wjr1zBWm/dTXVpy77332pIlS+ytt96y22+/3SZMmGDZbNbeeuut2r7/+q//arvttpsVCoXatiRJ7IADDrBp06bVtn31q18dtPw+SRIzM7v22mtNkv385z+vPVYqlWz//fe3pqYma29vN7OVyyXGjRtny5cvr+171113mST71a9+ZWbpcgmtsux7IIMtW6j+HGbOnGlRFPV57LTTTrNtttmm33NWXUrx0ksvmed5duyxx9aWSaz6uVd3DN/4xjessbHRXnzxxT7bL774YvN93958800zM7vzzjtNkn3nO9+p7RNFkR144IFDWloyZswY22OPPVa7T1VHR4eNHj3azjzzzD7bFy5caKNGjeqzfX0sdTNb/dKS6dOn2yGHHNJv+3PPPWeS7Ec/+pGZmf3iF78wSXbjjTf22e9HP/qRSbJ3v/vdqz2G6hLfVZcI90ark+FD3Or7cyBu9bUx4tY111xT+288Y8YMu+mmm+y6666ziRMn2pgxY2z+/PkDPm91rU6qjz3wwAP9Hjv++ONt0qRJte+HGgsxPIhRfX8OxKi+NkaM+t73vmeS7MEHH+yz/eKLLzZJdtRRRw363O9///smyX7zm98M+PjDDz9skmpfO+20U7+Ytttuu9mOO+7Y5797sVi0KVOmmCS7/fbbV3v82LCIUX1/DsSovjbW9d+CBQusubnZfvzjH5vZ4C0vGhsb7fTTT+/3/F//+tcmyX7729/Wtj366KO2/fbb94lRe++9d62NQNUtt9xis2fPtp/97Gf2y1/+0i655BJraGiw8ePH137m2DiIR31/DsSjvuotH2Vm9oMf/MBGjRplixcvNjMbsNXJHXfcUfu9mTZtms2ZM8fmzJlj06ZNs0wm06f1bm+ra3Xy1FNP1V5z4sSJdt1119lNN91kM2bMMOec3XPPPWv1OTe0umx1cuihh2rChAnaeuutddxxx6mxsVF33313bXnF8uXL9Yc//EEnnHCCOjo6tHTpUi1dulTLli3T4Ycfrpdeeqk2dfcXv/iF9thjj9odt96qSzF+85vfaNKkSTrxxBNrj4VhqM997nPq7OzUn/70pz7P+9jHPtbnbl916curr74qKa3szWQy+uMf/6gVK1as88/hzDPPlO/76/TcasXfV7/61X53pAdagrKq2267TQceeKDGjBlT+/kuXbpUhx56qOI41gMPPCAp/dkFQaBzzjmn9lzf9/XZz352SMfZ3t6u5ubmIe07d+5ctba26sQTT+xzTL7va99999X9998/pNdZX3p6epTNZvttrw686OnpkSQdccQR2mabbXTRRRfpjjvu0BtvvKFbb71VX/nKVxQEQW2/gfzjH//Qeeedp/3333/Idy0xPIhbKeJWXxsjblWX6TvndN999+mkk07SOeecozvvvFMrVqzQD3/4w7V+zWpcGizG9Y5bQ42FGF7EqBQxqq+NEaNOOukkjRo1Sqeffrrmzp2r119/XTfccIOuu+46SauPETfffLMmTJig97///QM+vuuuu2ru3Lm688479cUvflGNjY39Wpece+65evHFF3XGGWfo+eef19///nedeuqpWrBgwRrfHxsPMSpFjOprY13/felLX9J2223Xb7juqtbmnGfMmDHac889dfHFF+vOO+/U1Vdfrddff13HH3+8CoVCbb8TTjhBc+bM0amnnqoPf/jD+sY3vqHf/e53WrZsma644or18vmwdohHKeJRX/WWj1q2bJm++tWv6tJLL9WECRMG3a96XtTR0aH77rtPs2fP1uzZs3XvvffKzPSd73xnrd+7+prLli3TXXfdpXPOOUcnnXSS7rvvPo0bN07f/OY31+1DbSB12erkhz/8oXbccUe1tbXpv/7rv/TAAw/0+R/Myy+/LDPTpZdeqksvvXTA11i8eLG23HJLvfLKK/roRz+62vd74403NG3atH5/IXfZZZfa471NmTKlz/fVoFMNKtlsVldddZUuvPBCTZw4Ufvtt5+OOuoonXrqqUPulyOp31TntfHKK6/I8zztuuuu6/T8l156SX/7298G/QtUndL6xhtvaPLkyWpqaurz+E477TSk92lpaan1QBvKMUnSIYccMuhrbUz5fH7APrrVE5nqstpcLqdf//rXOuGEE2q/i9lsVt/5znd0xRVX9PvZVS1cuFBHHnmkRo0aVeuhi/pF3EoRt/ofk7Rh41Y11hx99NF9PtN+++2nqVOn6qGHHlrn1xwsxvVuGzDUWIjhRYxKEaP6H5O0YWPUpEmTdPfdd+uUU07RYYcdVnvd73//+zrttNMGPQ969dVX9fDDD+szn/mMgmDgS5aWlhYdeuihkqRjjjlGN998s4455hg99dRT2mOPPSRJZ599tt566y1997vfrfVL3WefffTFL35xtedh2LiIUSliVP9jkjZsjHrkkUd044036r777ltjX92hnvO0tbXpwAMP1Be+8AVdeOGFtf322WcfHXTQQZozZ06fRN2qZs6cqX333Vf33nvvunwkvEPEoxTxqP8xSfWTj7rkkks0duzYNSb5q3Hpfe97X5/2k1OmTNHMmTPf0bXi1KlTte+++9a2NzU16eijj9bP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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plot_reconstruction_comparison(model, test_data)" + ] + }, + { + "cell_type": "markdown", + "id": "8f88d0ee", + "metadata": {}, + "source": [ + "Finally, we can use the model to extract features that capture variation in subcellular protein localization for datasets of arbitrary size without any additional training or Wasserstein computations. We can perform the same analyses with this feature space as we would with the CellAligner-OT localization space including clustering, visualization, etc." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "edaa7b88", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Extracting embeddings for 1670 unique images from PairedDataset...\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Extracting embeddings: 100%|██████████| 27/27 [01:18<00:00, 2.92s/it]\n" + ] + }, + { + "data": { + "text/plain": [ + "(1670, 50)" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "embeddings = extract_embeddings(model, test_data)\n", + "embeddings.shape" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "827f1b72", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/opt/conda/lib/python3.10/site-packages/plotly/express/_core.py:1992: FutureWarning: When grouping with a length-1 list-like, you will need to pass a length-1 tuple to get_group in a future version of pandas. Pass `(name,)` instead of `name` to silence this warning.\n", + " sf: grouped.get_group(s if len(s) > 1 else s[0])\n" + ] + }, + { + "data": { + "text/html": [ + " \n", + " " + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.plotly.v1+json": { + "config": { + "plotlyServerURL": "https://plot.ly" + }, + "data": [ + { + "hovertemplate": "%{hovertext}

color=plasma membrane
x=%{x}
y=%{y}", + "hovertext": [ + "1893_J14_31_5", + "1893_J14_31_9", + "1893_J14_33_4", + "203_B4_1_1", + "203_B4_1_7", + "204_B4_1_5", + "104_D9_1_8", + "121_A8_2_4", + "127_C11_1_10", + "127_C11_2_2", + "128_C11_1_4", + "128_C11_1_9", + "128_C11_2_4", + "128_C11_2_5", + "129_C11_2_8", + "628_F4_1_14", + "628_F4_2_5", + "628_F4_2_10", + "630_F4_1_12", + "609_F2_5_8", + "143_H4_2_9", + "144_H4_1_11", + "145_H4_2_6", + "175_G2_2_7", + "176_G2_1_6", + "808_F1_6_5", + "821_D4_1_2", + "821_D4_2_7", + "821_D4_2_15", + "855_F1_1_11", + "108_B8_1_11", + "643_B2_1_12", + "643_B2_3_8", + "644_B2_1_6", + "644_B2_3_2", + "644_B2_3_4", + "644_B2_3_5", + "645_B2_2_4", + "645_B2_2_6", + "645_B2_2_7", + "645_B2_2_9", + "645_B2_2_10", + "645_B2_2_11", + "87_B8_1_13", + "87_B8_2_4", + "945_B2_1_2", + "945_B2_1_4", + "945_B2_1_5", + "945_D3_1_3", + "945_D3_3_4", + "946_B2_2_3", + "946_B2_2_6", + "946_D3_4_4", + "953_A2_4_9", + "953_A2_5_9", + "953_B2_2_7", + "634_C6_1_2", + "634_C6_1_6", + "634_C6_1_11", + "635_C6_4_3", + "639_C6_3_4", + "639_C6_9_5", + "639_C6_9_8", + "522_F6_4_4", + "527_F6_1_7", + "529_F6_1_2", + "529_F6_2_7", + "33_C8_2_7", + "34_C8_1_5", + "924_A5_2_7", + "924_A5_2_8", + "924_A5_2_18", + "126_G6_2_9", + "761_A9_1_6", + "769_A9_1_8", + "769_A9_2_5", + "769_A9_2_14", + "192_F11_1_6", + "194_F11_1_7", + "403_F9_1_14", + "403_F9_1_17", + "406_F9_2_9", + "408_F9_2_6", + "408_F9_2_7", + "411_E2_2_6", + "411_E2_2_9", + "415_E2_1_1", + "415_E2_1_6", + "416_E2_1_7", + "416_E2_2_11", + "26_E9_2_3", + "26_E9_2_4", + "27_E9_2_4", + "27_E9_2_5", + "27_E9_2_7", + "28_E9_1_4", + "330_F5_2_3", + "331_F5_1_2", + "331_F5_1_3", + "534_A7_1_2", + "616_C6_1_3", + "616_C6_2_11", + "616_C6_2_13", + "619_C6_3_6", + "619_C6_3_10", + "619_C6_3_12" + ], + "legendgroup": "plasma membrane", + "marker": { + "color": "#1F77B4", + "symbol": "circle" + }, + "mode": "markers", + "name": "plasma membrane", + "showlegend": true, + "type": "scattergl", + "x": [ + 11.437403678894043, + 0.9753666520118713, + 5.412958145141602, + 0.7044373750686646, + 0.644619882106781, + 3.7471165657043457, + 4.061347961425781, + 3.6586601734161377, + 11.79686450958252, + 5.832003116607666, + 3.871305227279663, + 8.839500427246094, + 9.447354316711426, + 3.0408411026000977, + 11.343116760253906, + 5.21724796295166, + 1.2729097604751587, + 9.691274642944336, + -1.1111730337142944, + 1.4131866693496704, + 6.445054531097412, + 1.3725026845932007, + -1.776132583618164, + -2.4633305072784424, + 1.8864562511444092, + 5.897722244262695, + -2.440277099609375, + -2.5847575664520264, + 4.721423149108887, + 2.2710928916931152, + -0.6730172038078308, + -1.6783615350723267, + -2.481581687927246, + 0.4589780867099762, + 3.0481154918670654, + 5.033864498138428, + 9.840714454650879, + 1.7739332914352417, + -2.128037929534912, + 3.1001789569854736, + 1.602852702140808, + 2.958256721496582, + 4.912827014923096, + -0.8501579165458679, + 8.404081344604492, + 3.6023354530334473, + 2.0455422401428223, + 3.0278706550598145, + -2.5447676181793213, + 2.9879989624023438, + 5.803518295288086, + 3.060000419616699, + -0.9152354598045349, + -1.149532675743103, + 2.999992847442627, + -0.13651371002197266, + 1.4880332946777344, + 2.567767858505249, + -0.7363086938858032, + 0.8363190293312073, + 3.3264000415802, + 6.880521774291992, + 5.91978645324707, + 9.405186653137207, + -1.8751944303512573, + -0.35842785239219666, + -1.4501771926879883, + -1.1379073858261108, + -0.2031283974647522, + -1.5081257820129395, + -1.6545953750610352, + 1.8209785223007202, + 1.37326979637146, + -1.9959094524383545, + -1.641442060470581, + 1.2992384433746338, + -2.4385926723480225, + 7.88951301574707, + -0.22781488299369812, + 9.566537857055664, + 0.32222241163253784, + 1.1269505023956299, + -0.8502129912376404, + 3.35174560546875, + 10.244566917419434, + 1.8939008712768555, + 3.230133533477783, + 3.6231608390808105, + 8.631754875183105, + 0.6324849128723145, + 8.0845365524292, + 1.249221920967102, + 4.006199836730957, + 0.2157762497663498, + -0.09391152113676071, + 10.655365943908691, + 1.9586269855499268, + 8.84089183807373, + -0.8341182470321655, + 2.0201961994171143, + -2.3033456802368164, + 3.3454809188842773, + 9.393486022949219, + -0.4702509939670563, + 4.0983662605285645, + 9.199991226196289 + ], + "xaxis": "x", + "y": [ + 7.936084270477295, + 9.704239845275879, + 9.141460418701172, + 10.250493049621582, + 10.151857376098633, + 9.1812744140625, + 10.091902732849121, + 10.03331184387207, + 8.355684280395508, + 10.822183609008789, + 10.873313903808594, + 7.967275619506836, + 8.068228721618652, + 10.985326766967773, + 7.822197914123535, + 7.809582233428955, + 9.267227172851562, + 7.858602046966553, + 9.878408432006836, + 10.777179718017578, + 8.023723602294922, + 10.406922340393066, + 10.146177291870117, + 9.866507530212402, + 10.324658393859863, + 9.35986614227295, + 9.886747360229492, + 9.872200012207031, + 9.926318168640137, + 11.072028160095215, + 10.521811485290527, + 9.934243202209473, + 9.8511962890625, + 10.352774620056152, + 8.713736534118652, + 7.866868495941162, + 11.119586944580078, + 8.64672565460205, + 9.831222534179688, + 8.546686172485352, + 8.909188270568848, + 8.339441299438477, + 7.897364139556885, + 9.708395004272461, + 8.057068824768066, + 8.93982219696045, + 8.533744812011719, + 9.264866828918457, + 9.82258129119873, + 8.92735481262207, + 8.466211318969727, + 8.489449501037598, + 10.478937149047852, + 9.831676483154297, + 7.913784027099609, + 9.86153507232666, + 9.814777374267578, + 8.598983764648438, + 10.406044960021973, + 10.563523292541504, + 10.901388168334961, + 8.758687019348145, + 7.985221862792969, + 7.465484142303467, + 10.177242279052734, + 10.123150825500488, + 9.815028190612793, + 10.274921417236328, + 9.921756744384766, + 10.005097389221191, + 10.114779472351074, + 8.876811981201172, + 9.927469253540039, + 9.839347839355469, + 10.184244155883789, + 10.469877243041992, + 9.827385902404785, + 8.090365409851074, + 10.330046653747559, + 8.025254249572754, + 10.313115119934082, + 10.115166664123535, + 9.337325096130371, + 9.881591796875, + 7.873258590698242, + 8.86505126953125, + 8.711174964904785, + 10.431783676147461, + 7.816223621368408, + 9.694914817810059, + 8.116555213928223, + 10.471688270568848, + 8.965024948120117, + 10.340896606445312, + 10.102538108825684, + 7.805060863494873, + 10.220816612243652, + 8.126763343811035, + 10.019498825073242, + 10.809520721435547, + 9.81965160369873, + 10.378925323486328, + 8.554923057556152, + 9.500971794128418, + 10.193597793579102, + 7.524752616882324 + ], + "yaxis": "y" + }, + { + "hovertemplate": "%{hovertext}

color=vesicles
x=%{x}
y=%{y}", + "hovertext": [ + "1894_C2_2_6", + "1894_C2_3_6", + "1894_C2_3_8", + "1894_C2_3_11", + "570_F10_1_4", + "570_F10_1_9", + "100_F5_1_5", + "100_F5_1_14", + "101_F5_1_12", + "101_F5_2_7", + "101_F5_2_18", + "658_F1_5_1", + "659_F1_4_5", + "659_F1_6_10", + "59_G12_1_3", + "59_G12_2_10", + "60_G12_1_4", + "61_G12_1_11", + "61_G12_2_12", + "68_G12_1_4", + "68_G12_2_2", + "68_G12_2_7", + "69_G12_1_3", + "69_G12_2_3", + "69_G12_2_8", + "91_G12_2_11", + "548_C9_3_9", + "548_C9_4_10", + "461_F11_2_6", + "461_F11_2_8", + "462_F11_1_4", + "462_F11_1_8", + "464_F11_1_8", + "464_F11_1_10", + "464_F11_1_11", + "464_F11_2_3", + "464_F11_2_11", + "562_D3_3_7", + "568_D3_1_10", + "568_D3_2_2", + "568_D3_2_5", + "575_D3_2_10", + "837_D1_1_7", + "215_C9_2_1", + "217_C9_1_5", + "217_C9_1_13", + "610_B10_1_11", + "610_B10_2_13", + "617_B10_1_2", + "617_B10_2_4", + "617_B10_2_9", + "180_H3_2_16", + "181_H3_2_9", + "189_E8_1_1", + "189_E8_1_2", + "189_E8_1_4", + "611_F6_1_4", + "618_F6_1_6", + "618_F6_3_7", + "57_E8_1_14", + "57_E8_2_10", + "58_E8_1_6", + "58_E8_2_10", + "1_H3_1_6", + "2_H3_1_12", + "2_H3_2_3", + "2_H3_2_4", + "234_G5_1_8", + "234_G5_2_2", + "234_G5_2_4", + "535_G5_1_5", + "62_G12_1_6", + "62_G12_1_12", + "62_G12_2_2", + "62_G12_2_4", + "62_G12_2_5", + "63_G12_2_3", + "93_G12_1_2", + "93_G12_1_6", + "421_F7_3_7", + "421_F7_3_14", + "421_F7_3_15", + "421_F7_4_14", + "427_F7_1_12", + "282_B11_2_9", + "283_B11_1_7", + "215_D12_1_5", + "216_D12_1_10", + "609_A7_8_12", + "615_G5_1_10", + "615_G5_1_12", + "615_G5_2_11", + "449_C6_3_21", + "449_C6_5_5", + "452_C6_3_5", + "452_C6_3_7", + "452_C6_3_8", + "455_C6_3_7", + "455_C6_4_4", + "20_A6_1_5", + "20_A6_1_7", + "20_A6_1_10", + "20_A6_2_5", + "21_A6_1_6", + "21_A6_2_6", + "22_A6_1_3", + "22_A6_2_8", + "20_F2_2_10", + "21_F2_2_3", + "21_F2_2_4", + "154_G9_1_3", + "154_G9_1_6", + "198_F9_2_6", + "105_G9_1_11", + "107_G9_1_7", + "107_G9_2_9", + "480_E5_1_7", + "510_E5_1_17", + "510_E5_1_22", + "510_E5_2_9" + ], + "legendgroup": "vesicles", + "marker": { + "color": "#FF7F0E", + "symbol": "circle" + }, + "mode": "markers", + "name": "vesicles", + "showlegend": true, + "type": "scattergl", + "x": [ + 9.285935401916504, + 8.188887596130371, + -2.1349825859069824, + 1.8502135276794434, + 3.700629711151123, + 5.424103736877441, + 3.513672113418579, + 6.7465643882751465, + 4.378493785858154, + -0.7859390377998352, + 10.192152976989746, + -1.03993558883667, + 10.872058868408203, + -0.035113196820020676, + 6.049213886260986, + 1.3545780181884766, + 5.305659770965576, + 1.8879388570785522, + 5.596472263336182, + 1.9471548795700073, + 11.907737731933594, + 9.179183006286621, + -1.1680008172988892, + 2.7808775901794434, + 3.60530948638916, + 10.39724349975586, + 7.479644775390625, + 10.161272048950195, + 7.378576278686523, + 4.451239585876465, + 5.005497455596924, + 3.140004873275757, + -0.6802276372909546, + 0.11550435423851013, + 9.235568046569824, + 9.834880828857422, + 1.7885063886642456, + 6.955780506134033, + -0.4366675615310669, + 7.296996116638184, + 7.347056865692139, + 3.147331714630127, + 8.8035306930542, + 9.553465843200684, + 1.432139277458191, + 11.767281532287598, + 6.615468502044678, + 9.383200645446777, + 6.807214736938477, + 0.9641739130020142, + 8.051589012145996, + -1.6589112281799316, + 4.783672332763672, + 3.41520357131958, + -1.7560995817184448, + -0.16280552744865417, + 7.946258068084717, + 3.0204873085021973, + 5.899518013000488, + 6.751859188079834, + 1.0665112733840942, + 7.366313934326172, + 0.9462012052536011, + 2.8204233646392822, + 2.9834346771240234, + -1.9932167530059814, + 6.607781887054443, + 2.2607932090759277, + 10.324588775634766, + 11.332099914550781, + 2.415109395980835, + 2.2832024097442627, + 2.387169122695923, + 6.7409257888793945, + 5.995389461517334, + 4.461305618286133, + 8.65954875946045, + 3.7143654823303223, + 3.5918898582458496, + 5.593886375427246, + 6.837454795837402, + 5.100203514099121, + 1.936654806137085, + 3.2464439868927, + 5.79866361618042, + 10.950220108032227, + -0.8656067848205566, + 7.19631290435791, + 2.841010093688965, + -0.5591171383857727, + 6.586352825164795, + 2.9887638092041016, + -0.14628253877162933, + 9.166753768920898, + 4.695462703704834, + 0.8004719614982605, + 9.159929275512695, + 4.934075355529785, + 4.509697914123535, + 10.878599166870117, + 5.082061290740967, + 7.3030290603637695, + -0.2727987766265869, + 1.6556165218353271, + -1.7240053415298462, + 2.7053751945495605, + 3.8205296993255615, + 4.844299793243408, + 2.3640060424804688, + 9.004129409790039, + 2.428449869155884, + 0.309872567653656, + 3.7069642543792725, + 3.058600902557373, + 6.140074253082275, + 7.282569885253906, + 11.224671363830566, + 8.972673416137695, + -0.39150699973106384, + 0.8405913710594177 + ], + "xaxis": "x", + "y": [ + 7.289881706237793, + 8.7659912109375, + 9.7770357131958, + 8.270730972290039, + 9.527826309204102, + 8.027181625366211, + 10.424854278564453, + 7.803881645202637, + 10.145621299743652, + 9.74114990234375, + 7.690700054168701, + 9.220887184143066, + 7.420437335968018, + 9.686607360839844, + 8.78044319152832, + 10.188528060913086, + 10.924087524414062, + 8.53693675994873, + 8.495870590209961, + 8.49335765838623, + 8.478896141052246, + 7.377534866333008, + 10.36387825012207, + 10.6628999710083, + 8.468499183654785, + 7.4372758865356445, + 7.719824314117432, + 7.274416446685791, + 7.724620819091797, + 8.947203636169434, + 7.821033477783203, + 8.868184089660645, + 9.957353591918945, + 9.710670471191406, + 7.826048851013184, + 7.364233016967773, + 10.413451194763184, + 9.131030082702637, + 9.233217239379883, + 7.816628456115723, + 10.327174186706543, + 9.030776023864746, + 10.590176582336426, + 8.165312767028809, + 10.444474220275879, + 8.099729537963867, + 8.736586570739746, + 8.048969268798828, + 8.896136283874512, + 10.299905776977539, + 8.038553237915039, + 9.955303192138672, + 8.838457107543945, + 9.309002876281738, + 9.471121788024902, + 9.140958786010742, + 9.467670440673828, + 9.211688995361328, + 9.847558975219727, + 7.894697189331055, + 10.316429138183594, + 7.698606491088867, + 9.82711124420166, + 9.387619972229004, + 9.48202896118164, + 9.602668762207031, + 9.099685668945312, + 9.294034004211426, + 7.508839130401611, + 7.840555191040039, + 9.198125839233398, + 8.043542861938477, + 8.16727066040039, + 7.905535697937012, + 9.071355819702148, + 8.35134220123291, + 10.681903839111328, + 10.253159523010254, + 9.983001708984375, + 9.079646110534668, + 8.83973503112793, + 8.378059387207031, + 10.316120147705078, + 9.405381202697754, + 9.751474380493164, + 8.695728302001953, + 9.660832405090332, + 8.615999221801758, + 8.052789688110352, + 10.503029823303223, + 11.108725547790527, + 9.361307144165039, + 10.352855682373047, + 7.784286975860596, + 8.839875221252441, + 10.362573623657227, + 8.25741958618164, + 8.546942710876465, + 8.920453071594238, + 8.173662185668945, + 7.828895568847656, + 7.738155364990234, + 10.17492389678955, + 9.707717895507812, + 10.170764923095703, + 8.01125717163086, + 9.750064849853516, + 8.711328506469727, + 9.283551216125488, + 7.891744613647461, + 8.5425443649292, + 10.230094909667969, + 10.961301803588867, + 8.805376052856445, + 9.735586166381836, + 10.058271408081055, + 7.829433441162109, + 7.636054992675781, + 9.765263557434082, + 10.243302345275879 + ], + "yaxis": "y" + }, + { + "hovertemplate": "%{hovertext}

color=peroxisomes
x=%{x}
y=%{y}", + "hovertext": [ + "376_A2_1_8", + "378_A2_1_7", + "378_A2_2_9", + "378_A2_2_10", + "383_A2_1_2", + "383_A2_2_1", + "383_A2_2_9", + "17_H7_2_4", + "17_H7_2_7", + "18_H7_2_6", + "19_H7_2_8", + "537_C1_1_7", + "540_C1_1_14", + "540_C1_1_16", + "319_B8_3_3", + "319_B8_3_12", + "319_H7_1_5", + "319_H7_1_9", + "319_H7_2_5", + "319_H7_2_6", + "320_H7_1_8", + "340_B8_1_17", + "340_B8_2_5", + "340_H7_2_3", + "221_C12_1_7", + "221_C12_1_19", + "221_C12_2_9", + "221_C12_2_10", + "234_G8_2_3", + "234_G8_2_7", + "234_G8_2_8", + "234_H8_1_6", + "234_H8_1_8", + "234_H8_2_3", + "234_H8_2_6", + "235_G8_1_12", + "235_G8_1_13", + "189_G2_1_3", + "189_G2_2_9", + "190_G2_1_9", + "190_G2_2_6", + "191_G2_1_3", + "191_G2_2_4", + "599_G11_1_12", + "601_G11_1_5", + "601_G11_2_7", + "603_G11_1_7", + "603_G11_2_12", + "616_F1_3_7", + "616_F1_4_8", + "619_F1_3_10", + "239_G8_1_4", + "240_G8_1_4", + "240_G8_2_12", + "241_G8_1_7", + "241_G8_2_5", + "241_G8_2_12" + ], + "legendgroup": "peroxisomes", + "marker": { + "color": "#2CA02C", + "symbol": "circle" + }, + "mode": "markers", + "name": "peroxisomes", + "showlegend": true, + "type": "scattergl", + "x": [ + 6.472165584564209, + 6.869111061096191, + 7.188961029052734, + 5.055633068084717, + 4.901391983032227, + 6.13953971862793, + 4.824241638183594, + 7.997182846069336, + 3.030001640319824, + -2.524125099182129, + 10.038616180419922, + 10.060962677001953, + -0.9788990616798401, + 7.207653999328613, + 4.925546169281006, + 7.4718828201293945, + -0.09779885411262512, + 8.963629722595215, + -1.4146732091903687, + -0.973852276802063, + -0.7711370587348938, + 1.4009915590286255, + -2.228506088256836, + 2.2348575592041016, + 0.07671178132295609, + 0.11310240626335144, + 2.6619460582733154, + 4.26826286315918, + 3.438533067703247, + 2.182927131652832, + 4.272791385650635, + 4.407793045043945, + -2.2373194694519043, + 10.332476615905762, + -0.2822438180446625, + 10.175993919372559, + 8.974346160888672, + 4.766129493713379, + 7.3680548667907715, + 9.267241477966309, + 9.489072799682617, + 1.8968555927276611, + 7.168770790100098, + -0.1053372323513031, + 4.911126136779785, + 2.3288686275482178, + 6.365429401397705, + 3.949941635131836, + 4.285129547119141, + 7.563950538635254, + -2.3498873710632324, + 5.872560024261475, + 5.297318935394287, + 11.096592903137207, + 6.80879545211792, + 2.5467498302459717, + 5.174282073974609 + ], + "xaxis": "x", + "y": [ + 8.732595443725586, + 7.8702898025512695, + 9.99589729309082, + 10.012714385986328, + 9.741880416870117, + 10.489699363708496, + 8.776191711425781, + 7.933918476104736, + 9.450212478637695, + 9.738481521606445, + 7.9263410568237305, + 7.82438325881958, + 9.68112850189209, + 8.965191841125488, + 10.045562744140625, + 8.392416000366211, + 10.033467292785645, + 7.905089855194092, + 10.093085289001465, + 9.654531478881836, + 10.514399528503418, + 9.307766914367676, + 9.947361946105957, + 8.702811241149902, + 10.144081115722656, + 9.988424301147461, + 9.030776977539062, + 9.965315818786621, + 9.975924491882324, + 8.980236053466797, + 9.639779090881348, + 10.104620933532715, + 9.934484481811523, + 7.43812894821167, + 9.33027458190918, + 7.191001892089844, + 7.391987323760986, + 8.47756290435791, + 9.2401123046875, + 8.046232223510742, + 7.262880802154541, + 8.487102508544922, + 7.812093257904053, + 10.635218620300293, + 7.869960784912109, + 9.690948486328125, + 8.399792671203613, + 11.35405445098877, + 10.388195991516113, + 9.872344017028809, + 9.909306526184082, + 10.12047004699707, + 11.057857513427734, + 8.65942668914795, + 10.089378356933594, + 9.019412994384766, + 10.62861156463623 + ], + "yaxis": "y" + }, + { + "hovertemplate": "%{hovertext}

color=mitochondria
x=%{x}
y=%{y}", + "hovertext": [ + "56_E12_1_11", + "57_E12_2_5", + "58_E12_1_4", + "58_E12_2_9", + "71_E5_1_3", + "71_E5_2_7", + "72_E5_1_6", + "72_E5_2_7", + "73_E5_1_16", + "73_E5_2_14", + "73_E5_2_16", + "567_E5_1_6", + "567_E5_1_7", + "567_E5_1_8", + "567_E5_1_9", + "562_C4_1_2", + "575_C4_2_5", + "575_C4_2_6", + "435_H3_1_8", + "435_H3_2_13", + "445_H3_1_8", + "445_H3_2_2", + "445_H3_2_12", + "448_H3_2_6", + "448_H3_2_15", + "636_C10_1_8", + "636_C10_1_14", + "636_C10_2_13", + "636_C10_2_15", + "637_C10_1_7", + "637_C10_1_9", + "778_H9_5_7", + "380_G5_1_3", + "382_G5_1_9", + "397_G5_2_12", + "188_C11_1_7", + "188_C11_1_10", + "188_C11_2_8", + "189_G4_1_3", + "189_G4_2_7", + "190_G4_1_5", + "190_G4_1_10", + "190_G4_2_5", + "191_G4_1_1", + "284_F11_1_9", + "284_F11_2_2", + "284_F11_2_6", + "284_F11_2_10", + "286_F11_2_10", + "286_F11_2_16", + "563_F8_1_3", + "563_F8_1_4", + "563_F8_2_1", + "563_F8_2_2", + "566_F8_2_11", + "17_B2_1_9", + "17_B2_2_4", + "149_B7_1_5", + "150_B7_2_6", + "151_B7_1_3", + "192_B8_1_2", + "192_B8_1_6", + "193_B8_1_13", + "193_B8_2_7", + "430_A3_2_6", + "432_A3_1_9", + "432_A3_3_2", + "441_A3_1_3", + "570_H4_3_7", + "570_H4_3_11", + "108_E5_1_2", + "108_E5_1_3", + "85_E5_1_4", + "85_E5_2_3", + "85_E5_2_4", + "85_E5_2_5", + "87_E5_2_8", + "222_F11_1_2", + "222_F11_1_5", + "222_F11_2_6", + "222_F11_2_11", + "222_F11_2_15", + "248_F11_1_4", + "270_D12_1_9", + "270_D12_1_14", + "271_D12_1_16", + "271_D12_2_1", + "272_D12_1_12", + "1912_B11_17_cr5c8627fbe4f4e_3", + "1912_B11_17_cr5c8627fbe4f4e_7", + "35_H11_1_2", + "36_H11_2_10", + "37_H11_1_2", + "14_A3_2_3", + "16_A3_2_9", + "43_A1_2_1", + "1199_G11_5_7", + "1199_G11_5_8", + "1199_G11_5_12", + "1199_G11_5_13", + "146_E8_1_2", + "146_E8_2_9", + "147_E8_2_3", + "74_G1_1_9", + "75_G1_1_4", + "75_G1_1_6", + "75_G1_2_7", + "76_G1_1_4", + "76_G1_1_11", + "76_G1_2_9", + "582_B10_1_8", + "582_B10_2_5" + ], + "legendgroup": "mitochondria", + "marker": { + "color": "#D62728", + "symbol": "circle" + }, + "mode": "markers", + "name": "mitochondria", + "showlegend": true, + "type": "scattergl", + "x": [ + 8.876245498657227, + 2.592487096786499, + 6.91989803314209, + 7.123671531677246, + 4.439266204833984, + 4.445375442504883, + -2.008667230606079, + 10.68436336517334, + 4.824851036071777, + 7.1045241355896, + 2.5305356979370117, + 6.350950717926025, + -2.0021302700042725, + 4.325897216796875, + 10.036189079284668, + 4.099804878234863, + 6.744747161865234, + 7.263899326324463, + 3.008392095565796, + -2.6602959632873535, + 0.8147668838500977, + 5.470724582672119, + 3.329603433609009, + 7.558781147003174, + 5.122820854187012, + 10.487521171569824, + 8.172492980957031, + 12.123702049255371, + 7.198514938354492, + 5.918031215667725, + 2.890904188156128, + 11.37468433380127, + 6.347843647003174, + 1.974286675453186, + 2.551262617111206, + 11.069531440734863, + 1.3419978618621826, + 2.02968430519104, + 2.03627610206604, + 4.750566482543945, + 10.364418983459473, + 4.623544692993164, + 11.74278450012207, + 8.99223804473877, + 8.174579620361328, + 4.8072710037231445, + -0.2908617854118347, + 4.289394378662109, + 5.733397960662842, + 7.214886665344238, + 11.099199295043945, + -0.12719126045703888, + 2.1853928565979004, + 2.9407875537872314, + 2.308027744293213, + 2.604381561279297, + -0.9825682044029236, + 7.268889904022217, + 2.054063320159912, + 6.805116653442383, + 9.154326438903809, + 10.378458976745605, + 3.3709988594055176, + 2.823060989379883, + -0.07893073558807373, + 3.6117122173309326, + 5.212403774261475, + 4.8054585456848145, + 2.685934066772461, + 1.4873015880584717, + 4.126687049865723, + 6.108668327331543, + -1.5714633464813232, + 10.567143440246582, + 9.957620620727539, + 6.78408145904541, + 7.35112190246582, + 3.6057562828063965, + 11.41600227355957, + 3.4389665126800537, + 4.265722751617432, + 2.6232917308807373, + 2.0049502849578857, + -0.8300260901451111, + 4.765443801879883, + 7.496279716491699, + 2.9706203937530518, + 3.281561851501465, + 7.206207752227783, + 10.750850677490234, + -1.1239618062973022, + 11.153931617736816, + 6.692214012145996, + 4.238689422607422, + 4.4721150398254395, + 6.815250873565674, + 4.306057929992676, + 4.341485023498535, + 2.4668378829956055, + -2.594738721847534, + 4.7967119216918945, + 2.793553113937378, + 5.6059136390686035, + 8.938620567321777, + 3.874255895614624, + 10.577744483947754, + 2.7244932651519775, + 1.6260573863983154, + 6.062060832977295, + 7.27086067199707, + 4.487593650817871, + 4.668821334838867 + ], + "xaxis": "x", + "y": [ + 7.506082534790039, + 8.167549133300781, + 7.8048577308654785, + 7.733267307281494, + 8.527247428894043, + 10.18327522277832, + 9.64476203918457, + 7.50034236907959, + 8.592729568481445, + 9.94688892364502, + 8.113268852233887, + 9.129034996032715, + 9.891816139221191, + 9.080912590026855, + 7.237348556518555, + 8.366143226623535, + 9.707283973693848, + 9.07557201385498, + 8.784873008728027, + 9.820612907409668, + 9.968594551086426, + 8.815163612365723, + 10.811614990234375, + 9.025642395019531, + 8.56679916381836, + 7.644589424133301, + 9.378528594970703, + 9.02999210357666, + 11.10313606262207, + 8.405207633972168, + 9.096006393432617, + 8.080013275146484, + 9.413065910339355, + 8.6026611328125, + 8.081436157226562, + 7.625970363616943, + 9.073594093322754, + 9.181245803833008, + 9.13595962524414, + 11.729829788208008, + 10.4729642868042, + 9.357112884521484, + 8.135985374450684, + 7.658276081085205, + 9.30790901184082, + 8.603901863098145, + 10.4024658203125, + 10.998247146606445, + 10.777832984924316, + 9.980413436889648, + 7.649085521697998, + 9.729503631591797, + 8.674214363098145, + 8.663740158081055, + 9.393211364746094, + 8.380891799926758, + 9.293384552001953, + 9.956907272338867, + 9.513540267944336, + 9.206315994262695, + 7.516440391540527, + 7.715972423553467, + 9.214858055114746, + 9.072473526000977, + 10.285920143127441, + 8.715740203857422, + 8.29838752746582, + 8.624533653259277, + 8.398615837097168, + 10.236687660217285, + 9.85976791381836, + 8.899551391601562, + 9.356600761413574, + 7.453137397766113, + 7.311354637145996, + 9.899303436279297, + 9.285529136657715, + 9.634527206420898, + 7.90163516998291, + 10.662906646728516, + 9.868156433105469, + 9.340903282165527, + 9.280007362365723, + 9.200821876525879, + 9.141107559204102, + 9.022119522094727, + 8.858643531799316, + 9.359962463378906, + 9.102997779846191, + 7.569579601287842, + 9.283219337463379, + 8.651147842407227, + 11.231049537658691, + 8.426332473754883, + 11.140820503234863, + 8.266864776611328, + 8.106871604919434, + 9.885316848754883, + 8.192584991455078, + 9.849528312683105, + 11.788207054138184, + 8.671976089477539, + 8.27782154083252, + 7.4269561767578125, + 8.388533592224121, + 7.609335422515869, + 9.161142349243164, + 9.5056791305542, + 9.179010391235352, + 9.918281555175781, + 9.848281860351562, + 11.748169898986816 + ], + "yaxis": "y" + }, + { + "hovertemplate": "%{hovertext}

color=golgi apparatus
x=%{x}
y=%{y}", + "hovertext": [ + "125_A11_2_5", + "126_A11_1_10", + "126_A11_1_14", + "126_A11_2_8", + "924_F5_2_7", + "924_F5_2_12", + "932_F5_5_4", + "932_F5_5_15", + "402_G10_5_8", + "366_A6_3_1", + "366_A6_3_7", + "370_A6_1_9", + "370_A6_2_5", + "370_A6_2_6", + "373_A6_2_6", + "373_A6_2_7", + "460_F7_1_10", + "460_F7_2_4", + "465_F7_1_5", + "465_F7_2_12", + "467_F7_1_14", + "467_F7_2_5", + "221_C7_1_7", + "221_C7_1_12", + "221_C7_2_7", + "221_C7_2_10", + "776_F3_1_9", + "899_F3_1_12", + "502_C6_2_7", + "502_C6_2_9", + "557_C6_1_7", + "557_C6_2_8", + "557_C6_2_10", + "557_C6_2_12", + "183_F8_1_8", + "183_F8_2_2", + "242_F8_3_8", + "242_F8_4_4", + "301_G12_2_12", + "342_G12_1_5", + "342_G12_1_9", + "397_D6_3_12", + "563_E12_2_4", + "563_E12_2_5", + "566_E12_4_2", + "566_E12_5_2", + "566_E12_5_5", + "569_E12_1_9", + "569_E12_5_2", + "563_H2_2_7", + "566_H2_2_8", + "569_H2_1_5", + "569_H2_2_14", + "88_C1_1_9", + "88_C1_2_6", + "88_C1_2_9", + "88_C1_2_10", + "89_C1_2_7", + "89_C1_2_12", + "90_C1_1_3", + "90_C1_1_9", + "1001_F7_1_11", + "433_B5_1_2", + "433_B5_2_11", + "440_B5_1_3", + "440_B5_2_9", + "973_A8_1_10", + "973_A8_2_18", + "992_F7_2_19", + "295_F7_1_9", + "296_F7_2_20", + "297_F7_2_5", + "377_F10_1_6", + "377_F10_2_3", + "377_F10_2_12", + "379_F10_1_9", + "379_F10_2_15", + "390_F10_2_5", + "551_A9_2_7", + "551_A9_2_8", + "554_A9_2_3", + "560_A9_2_4", + "921_A12_1_9", + "921_A12_1_12", + "931_A12_1_10", + "620_A4_1_8", + "620_A4_5_5", + "620_A4_5_6", + "620_A4_5_7", + "620_A4_5_9", + "623_A4_3_9", + "627_A4_1_11", + "911_C10_1_7", + "912_C10_3_3", + "912_C10_3_9", + "918_A8_1_10", + "918_A8_2_7", + "486_F3_1_7", + "490_F3_1_3", + "490_F3_2_3", + "509_F3_1_11", + "509_F3_1_22", + "509_F3_2_10", + "509_F3_2_20", + "509_F3_2_22", + "509_F3_2_23", + "666_E7_1_9", + "666_E7_1_15", + "666_E7_2_9", + "669_E7_1_2", + "669_E7_2_7", + "671_E7_2_9", + "41_G10_1_5", + "41_G10_1_7", + "41_G10_2_9", + "42_G10_1_5", + "43_G10_2_4", + "43_G10_2_6", + "1244_G6_2_7", + "1244_G6_3_4", + "1244_G6_3_6", + "198_H8_1_4", + "198_H8_2_11" + ], + "legendgroup": "golgi apparatus", + "marker": { + "color": "#9467BD", + "symbol": "circle" + }, + "mode": "markers", + "name": "golgi apparatus", + "showlegend": true, + "type": "scattergl", + "x": [ + 5.004769325256348, + 6.885021209716797, + 2.5892746448516846, + 4.113199710845947, + 1.5546205043792725, + 11.063040733337402, + 11.625768661499023, + 5.366086959838867, + -0.6872038245201111, + 11.372458457946777, + 5.6419572830200195, + 9.882997512817383, + 10.720466613769531, + 6.625646114349365, + 6.331587314605713, + 0.6520422101020813, + 8.500747680664062, + 1.4282920360565186, + 4.633726596832275, + 3.6322455406188965, + 0.44843366742134094, + 5.692556858062744, + 1.8695635795593262, + 4.854382514953613, + 7.116418838500977, + 8.344352722167969, + 3.586064338684082, + 7.9069743156433105, + 4.28118371963501, + 7.883293151855469, + 6.260671138763428, + 7.074736595153809, + 6.772404670715332, + 11.87542724609375, + 9.616998672485352, + -0.19140402972698212, + 1.8278220891952515, + 11.13999080657959, + 4.78341817855835, + 7.318698883056641, + 4.397075176239014, + 6.60101842880249, + 9.862390518188477, + 5.766507148742676, + 9.529487609863281, + 0.051704928278923035, + 1.430942177772522, + 9.626619338989258, + 4.420607566833496, + 7.392518997192383, + 6.884763240814209, + 2.9589645862579346, + -1.6828659772872925, + 6.226981163024902, + 9.230490684509277, + 6.3324151039123535, + 4.440446376800537, + -0.4694593846797943, + 8.809886932373047, + 5.745002746582031, + 5.855489730834961, + 1.5564297437667847, + 4.605833053588867, + 5.538403034210205, + 3.033667802810669, + 0.5195502638816833, + 1.4377572536468506, + 5.498476028442383, + 3.7187488079071045, + 3.8171751499176025, + 9.619446754455566, + 4.329771041870117, + 4.624619960784912, + 7.876868724822998, + 11.729756355285645, + 6.482407093048096, + 4.316767692565918, + 5.708987712860107, + 11.590185165405273, + 7.847529411315918, + 3.056454658508301, + 10.861926078796387, + -0.43476107716560364, + 7.443984031677246, + 6.187744140625, + -2.542734146118164, + 5.505887508392334, + 12.234068870544434, + 9.688319206237793, + 4.874911308288574, + 6.314250469207764, + 4.226566314697266, + 1.1826478242874146, + 6.0188493728637695, + 4.232884883880615, + 7.381350517272949, + 3.790065050125122, + 6.248597621917725, + 8.648795127868652, + 9.368646621704102, + 1.7166513204574585, + -1.0323700904846191, + 11.690174102783203, + 11.734078407287598, + 11.297609329223633, + 10.098816871643066, + 12.624245643615723, + 6.4781999588012695, + 7.4839186668396, + 5.504943370819092, + 8.867866516113281, + 4.2560505867004395, + 6.360586643218994, + 2.758881092071533, + 9.936935424804688, + 9.382822036743164, + 2.249675989151001, + 10.382281303405762, + 6.372960567474365, + 5.175435543060303, + 6.775574684143066, + 8.826420783996582, + 5.570595741271973 + ], + "xaxis": "x", + "y": [ + 8.863319396972656, + 7.86146354675293, + 7.949977397918701, + 8.081941604614258, + 10.633055686950684, + 7.616848945617676, + 8.072561264038086, + 8.551000595092773, + 10.400092124938965, + 7.775071620941162, + 10.486204147338867, + 7.589285373687744, + 7.468497276306152, + 8.710862159729004, + 9.796310424804688, + 9.28459644317627, + 8.510796546936035, + 9.847847938537598, + 8.648054122924805, + 8.568385124206543, + 9.685846328735352, + 9.293923377990723, + 9.446496963500977, + 10.384177207946777, + 10.308466911315918, + 10.655710220336914, + 9.149362564086914, + 8.168458938598633, + 11.16461181640625, + 8.206631660461426, + 9.159958839416504, + 8.489871978759766, + 9.709561347961426, + 9.708334922790527, + 8.345226287841797, + 9.957003593444824, + 9.456904411315918, + 9.155204772949219, + 11.763157844543457, + 9.850730895996094, + 11.077397346496582, + 8.231725692749023, + 7.4063029289245605, + 9.78343391418457, + 8.270895004272461, + 9.608264923095703, + 9.810025215148926, + 7.25045919418335, + 9.857603073120117, + 9.17324161529541, + 10.120267868041992, + 9.927001953125, + 9.926275253295898, + 9.27032470703125, + 7.254623889923096, + 9.363168716430664, + 8.60366439819336, + 10.406930923461914, + 7.659491062164307, + 8.587879180908203, + 8.58338737487793, + 10.284642219543457, + 8.81346321105957, + 11.273054122924805, + 10.824094772338867, + 9.56298828125, + 9.849698066711426, + 9.768850326538086, + 11.127902030944824, + 11.12601089477539, + 8.600180625915527, + 10.29735279083252, + 9.463370323181152, + 8.188559532165527, + 8.388129234313965, + 8.93686580657959, + 9.919442176818848, + 9.764117240905762, + 8.112099647521973, + 8.629714965820312, + 9.285490989685059, + 8.687845230102539, + 10.175190925598145, + 7.818667888641357, + 9.282398223876953, + 9.755931854248047, + 9.680213928222656, + 9.040457725524902, + 8.351082801818848, + 11.76307201385498, + 9.074119567871094, + 8.464370727539062, + 9.946414947509766, + 9.71793270111084, + 10.32390022277832, + 8.892991065979004, + 10.101558685302734, + 10.355133056640625, + 9.8796968460083, + 7.43780517578125, + 9.139591217041016, + 10.028444290161133, + 8.668807029724121, + 8.012223243713379, + 8.686506271362305, + 7.576552391052246, + 8.080733299255371, + 8.967719078063965, + 9.24197769165039, + 9.911789894104004, + 7.354523658752441, + 10.026108741760254, + 9.16253662109375, + 7.855747222900391, + 7.608037948608398, + 7.956940650939941, + 8.128968238830566, + 10.463541030883789, + 10.678622245788574, + 8.651168823242188, + 10.006348609924316, + 7.589323997497559, + 8.86194133758545 + ], + "yaxis": "y" + }, + { + "hovertemplate": "%{hovertext}

color=actin filaments
x=%{x}
y=%{y}", + "hovertext": [ + "624_C6_1_5", + "624_C6_1_10", + "516_F6_2_3", + "516_F6_2_10", + "519_F6_7_2", + "519_F6_7_5", + "519_F6_7_12", + "556_F6_1_8", + "556_F6_1_9", + "556_F6_2_14", + "404_E3_5_12", + "407_E3_1_3", + "407_E3_1_4", + "407_E3_1_9", + "407_E3_1_10", + "410_E3_1_10", + "410_E3_4_10", + "411_B4_2_4", + "411_B4_2_11", + "411_B4_2_16", + "411_B4_3_5", + "415_B4_1_11", + "415_B4_2_2", + "583_F3_1_2", + "583_F3_1_4", + "598_F3_2_9", + "598_F3_2_12", + "598_F3_3_5", + "296_E6_2_6", + "296_E6_2_7", + "32_B6_1_7", + "33_B6_1_5", + "33_B6_1_9", + "33_B6_1_13", + "33_B6_2_12", + "34_B6_2_5", + "34_B6_2_10", + "34_B6_2_13", + "620_C5_1_11", + "620_C5_3_10", + "623_C5_4_7", + "627_C5_2_8", + "627_C5_2_15", + "813_B4_3_2", + "813_B4_3_3", + "475_E2_1_5", + "475_E2_1_7", + "477_E2_1_5", + "477_E2_1_9", + "477_E2_2_11", + "477_E2_2_13", + "479_E2_3_5", + "479_E2_3_10", + "757_E10_5_12", + "757_E10_7_14", + "769_E10_2_11", + "474_A12_2_8", + "474_A12_3_7", + "510_A12_2_3", + "510_A12_2_7", + "510_A12_2_9", + "510_A12_2_10", + "143_F2_1_3", + "143_F2_2_4", + "144_F2_1_6", + "145_F2_1_6", + "145_F2_2_10", + "616_F12_1_8", + "616_F12_2_7", + "616_F12_2_9", + "619_F12_1_5", + "619_F12_1_10" + ], + "legendgroup": "actin filaments", + "marker": { + "color": "#8C564B", + "symbol": "circle" + }, + "mode": "markers", + "name": "actin filaments", + "showlegend": true, + "type": "scattergl", + "x": [ + -0.051113713532686234, + -0.3435332477092743, + 5.0222697257995605, + 10.331456184387207, + 6.394937038421631, + 2.817870616912842, + 6.886628150939941, + 6.957649230957031, + 2.257040023803711, + -0.20112694799900055, + 3.4199514389038086, + 9.902653694152832, + 10.442671775817871, + 11.46597957611084, + 10.358049392700195, + 0.5125899314880371, + 3.6177830696105957, + 5.471142768859863, + 6.270131587982178, + 4.804708480834961, + -1.2990058660507202, + -1.4311481714248657, + 2.7070982456207275, + 11.526765823364258, + 4.160569667816162, + -1.592599868774414, + 9.404735565185547, + 0.5261075496673584, + 10.244186401367188, + -0.5476545095443726, + 3.653048276901245, + 4.7813591957092285, + -1.0907959938049316, + 3.370614767074585, + 0.2707626223564148, + 3.567871570587158, + 4.9233269691467285, + 4.275055885314941, + 4.538545608520508, + 1.699578881263733, + -1.0350489616394043, + 2.707329750061035, + 3.6574594974517822, + 7.553219318389893, + 3.2026891708374023, + 3.144845724105835, + -0.8374470472335815, + -1.6496148109436035, + 7.4375810623168945, + 10.336009979248047, + 6.328372001647949, + 1.7112127542495728, + 2.8879940509796143, + 2.018975257873535, + 1.6146135330200195, + 0.4613816738128662, + 2.9811699390411377, + 3.550312042236328, + 1.972017526626587, + 2.6542248725891113, + 3.5887928009033203, + 3.7342910766601562, + 5.1894426345825195, + -2.5406365394592285, + -2.4606575965881348, + 5.385213375091553, + 0.23866920173168182, + 11.233489990234375, + 6.903780460357666, + 2.9462454319000244, + 9.701461791992188, + -2.634294271469116 + ], + "xaxis": "x", + "y": [ + 9.81507682800293, + 9.916971206665039, + 7.769481182098389, + 7.403113842010498, + 7.979330539703369, + 8.073902130126953, + 8.694353103637695, + 7.574337482452393, + 9.50735855102539, + 9.38923168182373, + 10.940258026123047, + 8.17578411102295, + 10.377908706665039, + 7.956019878387451, + 7.511343955993652, + 9.587531089782715, + 10.204992294311523, + 9.024825096130371, + 8.610971450805664, + 8.763811111450195, + 10.375728607177734, + 10.2976655960083, + 8.055310249328613, + 8.047897338867188, + 9.942087173461914, + 10.263806343078613, + 8.48245906829834, + 10.113971710205078, + 7.828304290771484, + 10.216398239135742, + 9.249751091003418, + 11.78860092163086, + 9.16310977935791, + 6.163523197174072, + 9.789214134216309, + 11.069075584411621, + 8.012373924255371, + 11.216279983520508, + 8.99679946899414, + 9.368553161621094, + 9.406460762023926, + 10.845699310302734, + 9.456710815429688, + 7.714722633361816, + 7.865786552429199, + 8.907540321350098, + 9.552569389343262, + 9.521617889404297, + 7.75559139251709, + 10.441733360290527, + 8.429061889648438, + 10.12252140045166, + 9.314643859863281, + 8.633573532104492, + 10.829501152038574, + 9.371220588684082, + 9.279938697814941, + 8.476906776428223, + 9.33568286895752, + 9.337800025939941, + 10.44753360748291, + 10.901453971862793, + 9.791974067687988, + 9.830583572387695, + 9.857592582702637, + 9.046849250793457, + 9.854467391967773, + 7.663816928863525, + 7.989266395568848, + 7.98513126373291, + 7.636916160583496, + 9.900033950805664 + ], + "yaxis": "y" + }, + { + "hovertemplate": "%{hovertext}

color=microtubules
x=%{x}
y=%{y}", + "hovertext": [ + "795_C9_7_5", + "795_C9_8_8", + "799_C9_1_5", + "394_A7_1_2", + "395_A7_2_6", + "399_A7_1_8", + "399_A7_2_6", + "411_G5_1_5", + "411_G5_1_7", + "415_G5_1_5", + "415_G5_2_7", + "416_G5_1_6", + "416_G5_2_4", + "416_G5_2_5", + "497_B1_1_12", + "497_B1_1_23", + "507_B1_1_14", + "918_B3_2_6", + "966_G6_1_6", + "197_B5_1_6", + "197_B5_1_11", + "197_B5_1_12", + "197_B5_2_2", + "151_F10_1_1", + "151_F10_1_5", + "151_F10_2_8", + "366_A7_1_5", + "366_A7_2_1", + "373_A7_2_10", + "652_D10_1_4", + "187_G7_1_3", + "187_G7_2_6", + "188_G7_3_5", + "563_C12_1_4", + "569_C12_2_13", + "569_C12_2_15", + "413_B2_1_7", + "413_B2_1_11", + "417_B2_1_9", + "481_B4_1_6", + "481_B4_1_10", + "487_B4_3_6", + "487_B4_3_7", + "491_B4_2_10", + "189_C11_2_6", + "191_C11_1_11", + "191_C11_2_6", + "563_B12_1_4", + "563_B12_2_7", + "569_B12_1_9", + "661_G2_1_3", + "661_G2_1_4", + "662_G2_1_8", + "662_G2_2_1", + "662_G2_2_9", + "662_G2_2_13", + "670_G2_1_7", + "670_G2_2_6", + "822_B12_1_6", + "822_B12_2_6", + "831_G9_1_8", + "831_G9_1_11", + "621_C12_3_6", + "673_E1_1_4", + "673_E1_2_6", + "673_E1_2_8", + "218_D9_2_9", + "218_D9_2_15", + "219_D9_2_6", + "220_D9_1_4", + "418_A11_2_8", + "418_A11_2_19", + "424_A11_1_7", + "424_A11_2_8", + "429_A11_2_6", + "263_E5_1_4", + "263_E5_2_8", + "263_E5_2_9", + "263_E5_2_10", + "263_E5_2_11", + "263_E5_2_15", + "263_E5_2_16", + "264_E5_2_7", + "277_E5_2_6" + ], + "legendgroup": "microtubules", + "marker": { + "color": "#E377C2", + "symbol": "circle" + }, + "mode": "markers", + "name": "microtubules", + "showlegend": true, + "type": "scattergl", + "x": [ + 7.80588960647583, + 6.711369037628174, + 3.0636415481567383, + 8.039419174194336, + 10.275031089782715, + 1.7174198627471924, + -2.495785713195801, + 1.2521352767944336, + 4.156789302825928, + -0.7419219613075256, + -1.105910301208496, + 3.770939350128174, + -2.4856207370758057, + -0.1851118505001068, + 8.998716354370117, + 0.8127406239509583, + 7.721927165985107, + -0.3615245223045349, + 0.8290137648582458, + 1.560547113418579, + 10.060449600219727, + -0.8983007669448853, + 6.435068130493164, + 1.5114468336105347, + 11.305437088012695, + 5.688301086425781, + 5.018712520599365, + 2.4154443740844727, + 5.449938774108887, + 0.8927267789840698, + 1.626383900642395, + 5.597720623016357, + 10.51032829284668, + -2.1094210147857666, + 5.577792167663574, + 5.256476402282715, + -1.804337501525879, + 0.6922854781150818, + 9.164814949035645, + 11.05317211151123, + 6.645759105682373, + 8.212655067443848, + -1.4229531288146973, + -1.0687475204467773, + -1.5924274921417236, + 5.634369373321533, + 5.787140369415283, + -1.2577248811721802, + -1.5316545963287354, + 3.5333383083343506, + 7.958313941955566, + 9.822720527648926, + 5.768285751342773, + -0.8591622710227966, + 0.006367618218064308, + 5.70219612121582, + -2.6235251426696777, + 2.086392641067505, + 1.7709888219833374, + -0.038378093391656876, + 1.3616269826889038, + 6.923687934875488, + 2.138993501663208, + 3.0413897037506104, + 7.885809898376465, + 2.8308277130126953, + 4.6334710121154785, + -2.582110643386841, + 1.847437858581543, + 0.15101473033428192, + 3.7457406520843506, + 7.389935493469238, + 10.797651290893555, + 0.9668118953704834, + 1.8321282863616943, + 9.939204216003418, + 8.821638107299805, + -0.3046288788318634, + 3.349409818649292, + 12.05246639251709, + 11.354207992553711, + 7.225831508636475, + -0.5577960014343262, + 0.20251964032649994 + ], + "xaxis": "x", + "y": [ + 8.039447784423828, + 9.260540008544922, + 10.784685134887695, + 7.981029033660889, + 7.587905406951904, + 10.654318809509277, + 9.903830528259277, + 10.032915115356445, + 9.05367660522461, + 9.401101112365723, + 9.454922676086426, + 9.183558464050293, + 9.836512565612793, + 10.353898048400879, + 7.515796184539795, + 10.0004301071167, + 8.174359321594238, + 9.273420333862305, + 9.66981029510498, + 9.865777969360352, + 7.302517890930176, + 9.935626029968262, + 8.056742668151855, + 10.334508895874023, + 7.783709526062012, + 8.334988594055176, + 7.997478485107422, + 9.292506217956543, + 10.96531867980957, + 9.964659690856934, + 8.976839065551758, + 8.707195281982422, + 7.954761505126953, + 9.555830955505371, + 8.694043159484863, + 7.832960605621338, + 10.227999687194824, + 9.879170417785645, + 7.394522666931152, + 8.289298057556152, + 8.9433012008667, + 7.913244247436523, + 10.034280776977539, + 9.14828109741211, + 10.332475662231445, + 8.790081977844238, + 10.717884063720703, + 9.540560722351074, + 10.03397274017334, + 8.463295936584473, + 8.097739219665527, + 8.479972839355469, + 8.200474739074707, + 10.313556671142578, + 9.981703758239746, + 8.435460090637207, + 9.853832244873047, + 9.411123275756836, + 8.847495079040527, + 9.300243377685547, + 10.532662391662598, + 7.772002696990967, + 9.324620246887207, + 9.270539283752441, + 8.119784355163574, + 8.910429000854492, + 8.99204158782959, + 9.860812187194824, + 8.855253219604492, + 10.294961929321289, + 8.594392776489258, + 7.754316329956055, + 7.370981693267822, + 10.115278244018555, + 8.85879898071289, + 7.734009742736816, + 8.173809051513672, + 9.424324035644531, + 9.195332527160645, + 7.453793048858643, + 7.80363655090332, + 7.695418357849121, + 10.511618614196777, + 9.5834379196167 + ], + "yaxis": "y" + }, + { + "hovertemplate": "%{hovertext}

color=cytosol
x=%{x}
y=%{y}", + "hovertext": [ + "104_F7_1_4", + "35_G12_2_4", + "35_G12_2_6", + "36_G12_1_2", + "36_G12_1_4", + "36_G12_1_6", + "37_G12_2_1", + "821_B5_1_3", + "323_B9_1_2", + "323_B9_1_12", + "323_B9_1_16", + "323_B9_2_2", + "326_B9_1_8", + "404_A2_1_12", + "404_A2_2_9", + "404_A2_2_17", + "407_A2_2_5", + "410_A2_1_5", + "410_A2_2_8", + "410_A2_2_13", + "68_F1_1_5", + "68_F1_1_12", + "68_F1_2_2", + "68_F1_2_8", + "69_F1_2_5", + "69_F1_2_7", + "69_F1_2_9", + "505_H6_1_5", + "505_H6_2_14", + "508_H6_1_7", + "508_H6_2_3", + "508_H6_2_13", + "508_H6_2_20", + "754_E6_3_8", + "754_E6_3_11", + "754_E6_4_14", + "813_E6_1_3", + "534_B3_1_5", + "534_B3_2_4", + "552_B3_1_5", + "295_E11_2_4", + "295_E11_2_6", + "295_E11_2_7", + "297_E11_1_6", + "10_G10_2_6", + "10_G10_2_9", + "10_G10_2_11", + "10_G10_2_15", + "10_G10_2_16", + "11_G10_1_8", + "15_G10_1_7", + "79_F2_1_2", + "79_F2_1_7", + "80_F2_1_6", + "80_F2_1_7", + "81_F2_2_15", + "629_E12_1_5", + "629_E12_1_8", + "631_E12_1_14", + "631_E12_2_11", + "181_F10_1_4", + "181_F10_2_6", + "182_F10_1_8", + "573_H11_4_7", + "589_H11_3_3", + "589_H11_3_4", + "589_H11_3_10", + "709_H11_1_7", + "709_H11_1_8", + "709_H11_1_13", + "709_H11_2_12", + "59_C7_1_9", + "59_C7_1_14", + "60_C7_1_1", + "60_C7_1_4", + "60_C7_2_8", + "61_C7_1_10", + "504_H6_2_5", + "555_H6_1_5", + "555_H6_2_8", + "976_H6_2_3", + "976_H6_2_16", + "1200_E11_3_9", + "1200_E11_4_17", + "131_D9_1_5", + "131_D9_2_8", + "132_D9_1_9", + "132_D9_1_11", + "132_D9_2_4", + "164_D9_1_3", + "164_D9_1_9", + "164_D9_2_3", + "164_D9_2_7", + "1773_E11_7_8", + "6_A9_1_6", + "6_A9_1_7", + "6_A9_2_6", + "6_A9_2_7", + "754_B4_1_13", + "758_B4_2_5", + "758_B4_3_3", + "490_F5_1_5", + "490_F5_2_5", + "509_F5_2_23", + "913_G7_1_8", + "914_G7_4_3", + "914_G7_4_5", + "919_G7_2_8", + "319_G1_2_7", + "340_G1_2_6", + "340_G1_2_21", + "340_G1_2_23", + "281_C8_1_9", + "281_C8_2_7", + "281_C8_2_12", + "282_C8_2_2", + "282_C8_2_3", + "282_C8_2_5", + "282_C8_2_6", + "283_C8_1_11", + "283_C8_1_13", + "283_C8_2_12", + "508_C1_1_11", + "508_C1_1_12", + "548_C1_2_4", + "611_C11_1_17", + "611_C11_2_14", + "614_C11_2_20", + "618_C11_2_5", + "94_A12_1_6", + "94_A12_2_10", + "539_A7_2_3", + "539_A7_3_7", + "552_A7_1_6", + "652_C9_2_2", + "652_C9_2_9", + "656_C9_3_6", + "5_H11_1_4", + "6_H11_1_11", + "1009_E3_2_9", + "1029_E3_2_3", + "428_B6_3_1", + "428_B6_4_6", + "440_B6_1_10", + "440_B6_4_6", + "860_B7_1_5", + "860_B7_1_6", + "860_B7_2_7", + "995_E3_2_11", + "995_E3_2_24", + "239_G1_1_8", + "239_G1_1_17", + "239_G1_2_5", + "239_G1_2_13", + "239_G1_2_19", + "240_G1_1_10", + "240_G1_2_7", + "240_G1_2_9", + "241_G1_2_10" + ], + "legendgroup": "cytosol", + "marker": { + "color": "#7F7F7F", + "symbol": "circle" + }, + "mode": "markers", + "name": "cytosol", + "showlegend": true, + "type": "scattergl", + "x": [ + -1.8024123907089233, + 4.451461315155029, + 5.554102420806885, + 11.124267578125, + 2.312922239303589, + 9.652252197265625, + 8.778571128845215, + -1.3600499629974365, + -1.335456371307373, + 7.443792343139648, + 1.9072165489196777, + 10.143373489379883, + 4.074429988861084, + 8.308758735656738, + 7.55338191986084, + 7.164207935333252, + 2.3305938243865967, + -0.2691679298877716, + 8.665098190307617, + 6.522381782531738, + 4.726909637451172, + 2.7915217876434326, + 3.274899482727051, + 2.1014389991760254, + 4.981446743011475, + -2.4730026721954346, + 1.7339131832122803, + -1.66078519821167, + 7.188760280609131, + 1.2140663862228394, + -2.3221852779388428, + -0.1465684473514557, + -0.3740013539791107, + 7.736973762512207, + 2.4519684314727783, + 1.2494360208511353, + 9.15802001953125, + 1.690032720565796, + 3.997241973876953, + 5.305149078369141, + 6.423083305358887, + 4.941730976104736, + 7.329478740692139, + 3.461712598800659, + 1.5590189695358276, + 7.282429218292236, + 1.2699123620986938, + 2.5298080444335938, + 8.143815040588379, + -1.0032477378845215, + 7.395720481872559, + 9.118342399597168, + -0.5033261179924011, + -1.0939444303512573, + 0.03565198928117752, + 5.048367500305176, + 2.3554723262786865, + 3.184877395629883, + 5.083219051361084, + -0.01192645262926817, + -0.12788037955760956, + 7.478898048400879, + 7.11168909072876, + 9.43112850189209, + 11.134060859680176, + 1.9467740058898926, + 7.546279430389404, + -0.7457696795463562, + 10.568892478942871, + 1.8510558605194092, + 2.1384620666503906, + 2.517458438873291, + -1.1648414134979248, + 3.5587780475616455, + 0.26146408915519714, + 6.409632682800293, + 5.85659122467041, + 5.66884708404541, + 0.31512686610221863, + 8.53899097442627, + 3.980095148086548, + -0.07047638297080994, + 5.692490100860596, + 4.172157287597656, + 2.26041316986084, + 4.655416488647461, + -2.3866090774536133, + 3.265900135040283, + -1.5875344276428223, + -0.7942299246788025, + 2.0432045459747314, + 0.011703758500516415, + 3.5113565921783447, + 6.293914794921875, + 4.408410549163818, + -0.016115786507725716, + 9.830148696899414, + 4.0782318115234375, + 7.72549295425415, + 5.093253135681152, + -0.5087053775787354, + 10.188730239868164, + 1.7562775611877441, + -1.635428547859192, + -0.6363064646720886, + 3.0202219486236572, + -1.240703821182251, + -0.6934219002723694, + 6.327619552612305, + 9.955622673034668, + 10.300748825073242, + 4.115579128265381, + 0.18165627121925354, + 9.022111892700195, + 0.512808084487915, + -1.069522500038147, + 9.822710037231445, + 9.360747337341309, + -1.7606308460235596, + 9.766042709350586, + 5.599393844604492, + 3.471282720565796, + 4.556669235229492, + 7.49871826171875, + 1.1108161211013794, + -1.0210896730422974, + 9.338658332824707, + -0.7244977355003357, + 2.578871011734009, + 6.5443315505981445, + 9.93696403503418, + 5.633239269256592, + -1.6291919946670532, + -0.6725142002105713, + 3.088209629058838, + 1.9178423881530762, + 10.648760795593262, + 7.567183017730713, + 10.699491500854492, + 3.3385825157165527, + 8.862885475158691, + 2.711973190307617, + 11.85015869140625, + 10.408352851867676, + 3.616262197494507, + 3.915334463119507, + -0.980894923210144, + 4.928647994995117, + 1.9619731903076172, + 1.696612000465393, + -0.9016175866127014, + 7.414289474487305, + -1.8858392238616943, + 8.331531524658203, + -0.9031376242637634, + -2.42097806930542, + -0.9828623533248901, + -2.3421082496643066, + 0.672944188117981 + ], + "xaxis": "x", + "y": [ + 9.508915901184082, + 7.902645587921143, + 10.984152793884277, + 7.662893772125244, + 9.096430778503418, + 7.916247844696045, + 7.939950466156006, + 10.073538780212402, + 10.177257537841797, + 8.784046173095703, + 8.846030235290527, + 7.914827346801758, + 11.092183113098145, + 9.417011260986328, + 9.625894546508789, + 9.074568748474121, + 9.73112678527832, + 9.924982070922852, + 7.679642200469971, + 10.327933311462402, + 8.115706443786621, + 9.40397834777832, + 9.416913986206055, + 9.194377899169922, + 11.761091232299805, + 9.738021850585938, + 8.467219352722168, + 9.81246566772461, + 8.496408462524414, + 9.844138145446777, + 9.848121643066406, + 10.209026336669922, + 9.3840913772583, + 9.333038330078125, + 9.48116683959961, + 10.494721412658691, + 7.450821876525879, + 9.49586296081543, + 8.636772155761719, + 7.847829818725586, + 10.700910568237305, + 8.995564460754395, + 9.524800300598145, + 9.816413879394531, + 10.501399040222168, + 9.948592185974121, + 9.970736503601074, + 8.428391456604004, + 7.978897571563721, + 9.61203384399414, + 9.619368553161621, + 7.386185169219971, + 9.352662086486816, + 9.61865520477295, + 9.582316398620605, + 9.416678428649902, + 9.830875396728516, + 8.745105743408203, + 7.925664901733398, + 10.079793930053711, + 9.35986042022705, + 7.807250022888184, + 8.791913032531738, + 7.810709476470947, + 7.69212007522583, + 8.758685111999512, + 8.3303804397583, + 9.68980884552002, + 7.593451499938965, + 8.886625289916992, + 8.855127334594727, + 10.856640815734863, + 10.006922721862793, + 10.9592866897583, + 10.080716133117676, + 8.816914558410645, + 10.742476463317871, + 8.547178268432617, + 9.606854438781738, + 7.809178352355957, + 10.122838973999023, + 9.368858337402344, + 9.66519546508789, + 10.30241870880127, + 9.3541259765625, + 8.136218070983887, + 9.861291885375977, + 9.433431625366211, + 9.621241569519043, + 9.373429298400879, + 8.982512474060059, + 10.287497520446777, + 8.531920433044434, + 8.652302742004395, + 8.87519645690918, + 10.56347370147705, + 8.32929801940918, + 9.905038833618164, + 10.039565086364746, + 8.002765655517578, + 10.562239646911621, + 7.581142425537109, + 10.74317455291748, + 9.51015853881836, + 10.489933013916016, + 9.32044506072998, + 10.011491775512695, + 9.157191276550293, + 8.183904647827148, + 7.242562294006348, + 7.733373641967773, + 8.5224027633667, + 10.235504150390625, + 7.446998119354248, + 10.679693222045898, + 9.214256286621094, + 7.520055770874023, + 7.5556817054748535, + 10.068808555603027, + 7.999142646789551, + 11.597563743591309, + 10.469236373901367, + 10.057270050048828, + 8.30539321899414, + 9.91469955444336, + 10.271504402160645, + 7.880212783813477, + 10.508559226989746, + 9.366129875183105, + 8.63044548034668, + 7.803362846374512, + 8.87388801574707, + 10.04879093170166, + 9.552556991577148, + 8.902053833007812, + 8.737015724182129, + 7.309913158416748, + 8.248804092407227, + 7.430069446563721, + 9.314717292785645, + 7.793577671051025, + 9.476190567016602, + 8.18304443359375, + 8.63797664642334, + 9.045339584350586, + 9.562917709350586, + 9.277194023132324, + 10.182193756103516, + 10.856216430664062, + 9.054398536682129, + 10.06442928314209, + 8.481124877929688, + 9.903305053710938, + 7.830940246582031, + 9.743711471557617, + 9.894811630249023, + 9.841387748718262, + 9.921014785766602, + 9.168588638305664 + ], + "yaxis": "y" + }, + { + "hovertemplate": "%{hovertext}

color=nuclear speckles
x=%{x}
y=%{y}", + "hovertext": [ + "605_F9_1_10", + "605_F9_2_10", + "606_F9_2_14", + "490_H3_1_2", + "490_H3_1_5", + "490_H3_2_4", + "509_H3_3_6", + "509_H3_3_9", + "776_C1_2_3", + "776_C1_2_8", + "776_C1_2_13", + "789_C1_8_14", + "899_C1_1_12", + "899_C1_3_9", + "1245_G5_2_3", + "1245_G5_2_6", + "620_B1_4_4", + "623_B1_1_9", + "623_B1_1_15", + "440_C6_2_4", + "440_C6_2_5", + "440_C6_3_12", + "975_B10_1_3", + "976_B10_1_6", + "976_B10_2_11", + "980_B10_1_7", + "980_B10_1_13", + "592_B5_1_1", + "592_B5_1_4", + "592_B5_2_5", + "592_B5_2_10", + "791_G11_2_4", + "791_G11_2_8", + "794_G11_2_8", + "798_G11_1_11", + "798_G11_2_9", + "798_G11_2_12", + "1015_E12_1_10", + "7_C7_2_2", + "8_C7_1_5", + "8_C7_1_9", + "791_F11_10_13", + "791_F11_4_8", + "798_F11_1_8", + "798_F11_1_12", + "798_F11_1_13", + "23_E7_2_5", + "24_E7_1_3", + "24_E7_2_2", + "25_E7_1_5", + "231_G5_1_7", + "231_G5_1_9", + "231_G5_1_11", + "523_E4_1_3", + "523_E4_1_4", + "523_E4_1_10", + "523_E4_2_6", + "371_A2_1_11", + "372_A2_1_5", + "372_A2_2_1", + "374_A2_2_3", + "975_D6_1_5", + "976_D6_3_5", + "976_D6_4_6", + "980_D6_1_9", + "980_D6_2_1", + "980_D6_2_8", + "522_C1_1_3", + "522_C1_2_7", + "529_C1_1_17", + "529_C1_2_5", + "563_E1_1_3", + "563_E1_1_6", + "563_E1_2_2", + "566_E1_2_9", + "569_E1_3_13", + "565_F1_1_12", + "570_F1_1_3", + "570_F1_1_8", + "584_F1_1_6", + "584_F1_1_11", + "7_F4_1_5", + "7_F4_2_5", + "7_F4_2_11", + "8_F4_2_4", + "9_F4_2_5", + "9_F4_2_9", + "1158_G12_2_8", + "924_F4_2_9", + "924_F4_2_25", + "932_F4_2_9", + "94_E2_1_4", + "94_E2_2_5", + "95_E2_1_9", + "96_E2_1_8", + "971_F4_2_7", + "526_E7_1_3", + "526_E7_1_5", + "526_E7_2_15", + "528_E7_1_7", + "528_E7_1_8", + "528_E7_1_11", + "528_E7_2_4", + "528_E7_2_8", + "545_E7_1_3", + "545_E7_2_9", + "545_E7_2_19", + "564_A9_2_2", + "572_A9_1_3", + "572_A9_1_7" + ], + "legendgroup": "nuclear speckles", + "marker": { + "color": "#BCBD22", + "symbol": "circle" + }, + "mode": "markers", + "name": "nuclear speckles", + "showlegend": true, + "type": "scattergl", + "x": [ + 12.07348918914795, + 12.258650779724121, + 5.731505393981934, + 11.482514381408691, + -0.4047311544418335, + 8.722942352294922, + 11.792634963989258, + 12.00670051574707, + 11.891463279724121, + 7.324677467346191, + -0.26100438833236694, + 9.842408180236816, + 9.540069580078125, + 7.439480781555176, + 11.802841186523438, + 10.884795188903809, + 9.331859588623047, + -0.7661015391349792, + 9.321563720703125, + 10.946555137634277, + 9.75358772277832, + 7.663273811340332, + 10.749035835266113, + 11.914824485778809, + 9.485074043273926, + 7.325840950012207, + 7.576280117034912, + 1.9041752815246582, + 7.144676685333252, + 11.047569274902344, + 12.152562141418457, + 5.284160137176514, + 10.854405403137207, + 10.60406494140625, + 11.122819900512695, + 11.83301067352295, + 10.956655502319336, + 2.8143234252929688, + 12.126448631286621, + 6.283559322357178, + 5.098883152008057, + 5.083515644073486, + 8.83227252960205, + 10.928166389465332, + 12.049291610717773, + 2.70245099067688, + 4.199507236480713, + 3.893756866455078, + 11.941826820373535, + 0.24276675283908844, + 11.917436599731445, + 9.356618881225586, + 11.67520523071289, + 1.6802159547805786, + 8.932186126708984, + 1.6010422706604004, + 9.514856338500977, + 11.564970016479492, + 9.404121398925781, + 9.225342750549316, + 8.868645668029785, + -0.498170405626297, + 3.154674768447876, + 1.7926476001739502, + 10.85551929473877, + 6.625469207763672, + 7.4571332931518555, + 8.604262351989746, + 3.74592924118042, + 12.127912521362305, + 4.903397560119629, + 1.3218611478805542, + 11.832420349121094, + 10.96159839630127, + 8.82496452331543, + 10.649346351623535, + 12.113605499267578, + 11.590597152709961, + 12.00750732421875, + 12.004422187805176, + 10.856193542480469, + 5.20032262802124, + 10.226353645324707, + 8.34816837310791, + 6.356680393218994, + 6.742821216583252, + 6.329071044921875, + 9.521099090576172, + 9.4801025390625, + 6.0064616203308105, + 10.664175987243652, + 9.47519302368164, + 10.751021385192871, + 10.567959785461426, + 10.875974655151367, + 7.4667067527771, + 9.643583297729492, + 9.277566909790039, + -1.1593974828720093, + 11.534015655517578, + 10.973685264587402, + 10.952529907226562, + 10.227410316467285, + 10.63571834564209, + 10.072620391845703, + 10.689868927001953, + 5.539884567260742, + 4.346114635467529, + 6.169941425323486, + 5.638885021209717 + ], + "xaxis": "x", + "y": [ + 9.769668579101562, + 9.302553176879883, + 11.489947319030762, + 9.579075813293457, + 10.426056861877441, + 11.285606384277344, + 8.690122604370117, + 9.511303901672363, + 9.577609062194824, + 10.701563835144043, + 9.75223445892334, + 11.241864204406738, + 10.975647926330566, + 11.429719924926758, + 9.515569686889648, + 10.873017311096191, + 10.307027816772461, + 9.24410629272461, + 10.37912368774414, + 11.170185089111328, + 11.38102912902832, + 11.376238822937012, + 11.208006858825684, + 9.822360038757324, + 11.449458122253418, + 11.220849990844727, + 10.937773704528809, + 9.854731559753418, + 11.295073509216309, + 8.746092796325684, + 9.31459903717041, + 11.70824909210205, + 11.041420936584473, + 11.055721282958984, + 11.002989768981934, + 10.194511413574219, + 11.071070671081543, + 10.801637649536133, + 9.535472869873047, + 10.848339080810547, + 11.685586929321289, + 11.695258140563965, + 11.247696876525879, + 10.99527645111084, + 9.752099990844727, + 11.140605926513672, + 11.385055541992188, + 11.434615135192871, + 9.440120697021484, + 10.191890716552734, + 9.66280746459961, + 8.113954544067383, + 9.159473419189453, + 10.145263671875, + 10.391977310180664, + 10.403448104858398, + 8.612196922302246, + 9.016134262084961, + 11.216992378234863, + 10.394493103027344, + 11.307125091552734, + 10.34500789642334, + 10.802389144897461, + 10.751440048217773, + 8.679640769958496, + 11.542872428894043, + 10.87038803100586, + 10.691458702087402, + 10.96208667755127, + 9.47337818145752, + 10.784551620483398, + 10.255051612854004, + 9.84312629699707, + 10.83326530456543, + 11.327277183532715, + 11.2515869140625, + 9.943229675292969, + 10.346728324890137, + 10.032685279846191, + 10.061532020568848, + 11.170510292053223, + 10.332603454589844, + 9.043111801147461, + 11.377455711364746, + 10.69517993927002, + 10.952410697937012, + 10.835789680480957, + 11.396109580993652, + 10.942700386047363, + 9.125511169433594, + 11.207005500793457, + 10.477555274963379, + 11.116769790649414, + 11.082820892333984, + 11.20341968536377, + 11.516622543334961, + 11.361526489257812, + 11.3812255859375, + 10.499716758728027, + 9.898343086242676, + 11.00788688659668, + 11.18524169921875, + 11.371463775634766, + 11.24754524230957, + 11.343498229980469, + 11.220648765563965, + 11.60568904876709, + 11.475564002990723, + 11.500349044799805, + 11.641995429992676 + ], + "yaxis": "y" + }, + { + "hovertemplate": "%{hovertext}

color=nucleoplasm
x=%{x}
y=%{y}", + "hovertext": [ + "608_F9_2_6", + "975_H1_1_6", + "975_H1_1_8", + "975_H1_2_6", + "976_H1_1_12", + "976_H1_1_18", + "976_H1_1_22", + "976_H1_4_8", + "980_H1_1_18", + "980_H1_2_10", + "486_H3_1_8", + "486_H3_2_6", + "542_A5_1_4", + "542_A5_2_7", + "544_A5_2_23", + "571_A5_1_4", + "221_H4_1_5", + "221_H4_1_7", + "221_H4_2_10", + "222_H4_1_5", + "222_H4_2_6", + "222_H4_2_12", + "222_H4_2_14", + "248_H4_1_3", + "248_H4_1_5", + "248_H4_1_6", + "248_H4_1_10", + "554_C2_2_13", + "554_C2_2_16", + "560_C2_4_5", + "2_B4_2_4", + "402_E5_1_3", + "402_E5_2_4", + "402_E5_2_6", + "405_E5_3_12", + "405_E5_3_18", + "409_E5_1_10", + "474_C8_1_5", + "474_C8_2_7", + "510_C8_1_13", + "728_H2_1_3", + "728_H2_1_13", + "319_C6_2_3", + "319_C6_2_7", + "319_C6_2_9", + "319_C6_2_12", + "894_B7_1_1", + "907_B7_1_8", + "907_B7_1_12", + "944_D6_1_11", + "952_D6_2_6", + "177_D8_1_12", + "827_A4_1_7", + "827_A4_1_8", + "827_A4_2_8", + "827_A4_2_14", + "827_A4_2_17", + "829_A4_2_7", + "411_D8_1_3", + "411_D8_2_4", + "415_D8_1_10", + "415_D8_2_9", + "416_D8_2_3", + "416_D8_2_6", + "416_D8_2_7", + "416_D8_2_9", + "416_D8_2_12", + "955_E9_1_11", + "972_E9_1_8", + "972_E9_2_4", + "972_E9_2_10", + "301_D8_2_9", + "342_D8_1_5", + "946_A6_3_2", + "564_H9_2_11", + "572_H9_1_8", + "572_H9_2_8", + "587_H9_2_7", + "587_H9_2_10", + "831_C7_2_7", + "944_G9_1_11", + "626_C5_2_2", + "632_C5_3_6", + "632_C5_3_15", + "633_C5_1_11", + "633_C5_2_5", + "633_C5_2_9", + "920_G8_1_12", + "920_G8_1_14", + "920_G8_2_14", + "920_G8_2_15", + "941_G9_1_2", + "941_G9_1_10", + "620_G12_1_11", + "620_G12_2_4", + "623_G12_2_3", + "623_G12_2_7", + "627_G12_1_9", + "627_G12_1_11", + "627_G12_2_12", + "230_G5_2_20", + "520_E4_1_8", + "255_H11_1_2", + "255_H11_2_5", + "256_H11_2_6", + "256_H11_2_19", + "154_F9_1_4", + "263_H6_1_4", + "263_H6_2_4", + "263_H6_2_6", + "264_H6_2_10", + "264_H6_2_13", + "277_H6_2_4", + "277_H6_2_5", + "911_C9_1_8", + "911_C9_2_3", + "912_C9_1_4", + "912_C9_2_4", + "912_C9_2_9", + "918_D7_3_11", + "918_D7_3_17", + "951_A2_1_6", + "951_A2_1_11", + "951_A2_1_13", + "975_D4_1_5", + "975_D4_2_8", + "976_D4_1_4", + "976_D4_1_11", + "976_D4_1_13", + "976_D4_2_3", + "976_D4_2_4", + "976_D4_2_9", + "976_D4_2_10", + "980_D4_1_12", + "980_D4_2_21", + "809_H5_1_5", + "809_H5_1_8", + "809_H5_2_11", + "819_H5_2_3", + "258_D6_1_5", + "258_D6_1_10", + "259_D6_1_9", + "259_D6_2_4", + "259_D6_2_7", + "259_D6_2_11", + "260_D6_1_11", + "260_D6_2_10", + "507_B9_2_2", + "511_B9_1_9", + "511_B9_1_11", + "511_B9_1_12", + "236_C4_1_6", + "237_C4_1_3", + "237_C4_1_4", + "237_C4_2_8", + "268_C4_2_9", + "62_A1_2_5", + "62_A1_2_11", + "62_A1_2_13", + "62_A1_2_17", + "93_A1_2_4" + ], + "legendgroup": "nucleoplasm", + "marker": { + "color": "#17BECF", + "symbol": "circle" + }, + "mode": "markers", + "name": "nucleoplasm", + "showlegend": true, + "type": "scattergl", + "x": [ + 10.984966278076172, + 8.073525428771973, + 10.601151466369629, + 7.441102027893066, + 5.127652645111084, + 1.7473933696746826, + -0.3599754571914673, + 2.770860433578491, + 10.6112060546875, + 7.019987106323242, + 4.1411356925964355, + 10.363632202148438, + 7.926427841186523, + 10.362897872924805, + 1.7487398386001587, + 9.156259536743164, + 2.8661158084869385, + 10.490422248840332, + 9.221872329711914, + 7.106359481811523, + 6.777750015258789, + 10.659823417663574, + 8.821587562561035, + 8.808148384094238, + 10.443461418151855, + 10.686222076416016, + 9.075057983398438, + 4.259021759033203, + 3.6375386714935303, + 10.493456840515137, + 6.2044758796691895, + 8.656903266906738, + 4.7217116355896, + 7.67918062210083, + 7.271231651306152, + 9.142016410827637, + 3.1148581504821777, + 5.6097025871276855, + 5.91672420501709, + 6.149641990661621, + 4.915364742279053, + 0.4376066327095032, + 5.251420021057129, + 6.46743106842041, + 10.588942527770996, + 10.904836654663086, + 5.269296646118164, + 4.432221412658691, + 8.492021560668945, + 10.849034309387207, + 2.8038315773010254, + 4.524566173553467, + 11.405352592468262, + 9.099053382873535, + 11.424493789672852, + 12.02547550201416, + 12.230738639831543, + 9.371925354003906, + 2.8917253017425537, + 7.417837142944336, + 4.254062652587891, + 5.941372394561768, + 5.88485050201416, + 5.095426082611084, + 5.703062057495117, + 1.451463222503662, + 7.356603145599365, + 4.550978183746338, + 7.418269157409668, + 7.437565326690674, + 11.752007484436035, + 2.5107789039611816, + 4.509511470794678, + 9.183511734008789, + 11.952510833740234, + 7.260678768157959, + 9.278373718261719, + 8.166519165039062, + 3.053792953491211, + 6.220591068267822, + 3.86327862739563, + 6.192848205566406, + 11.386683464050293, + 12.031464576721191, + 9.562540054321289, + 7.172672271728516, + 11.945749282836914, + 7.068431854248047, + 1.260867953300476, + 6.767836093902588, + 6.531129837036133, + 7.12057638168335, + 5.975885391235352, + 10.798945426940918, + 9.558353424072266, + 9.203119277954102, + 9.257644653320312, + 8.00302791595459, + 7.102749824523926, + 12.256002426147461, + 8.157100677490234, + 9.13269329071045, + 8.34151840209961, + 6.952149391174316, + 10.846931457519531, + -1.1770893335342407, + 3.4477133750915527, + 6.76606559753418, + 11.070500373840332, + 10.420467376708984, + 9.36457633972168, + 10.93181324005127, + 11.962579727172852, + 7.544419765472412, + 9.255876541137695, + 12.120798110961914, + 9.071754455566406, + 10.264166831970215, + 9.964442253112793, + 4.145314693450928, + 10.944267272949219, + 7.7115702629089355, + 9.02327823638916, + 6.065130710601807, + 10.888262748718262, + 5.613483428955078, + 12.116403579711914, + 12.085001945495605, + 8.938956260681152, + 10.300929069519043, + 10.254083633422852, + 8.46902084350586, + 8.785200119018555, + 9.424691200256348, + 11.64520263671875, + 11.571700096130371, + 9.216139793395996, + 7.539426803588867, + 5.289574146270752, + 5.978673934936523, + 9.56047248840332, + 3.0805141925811768, + 9.509605407714844, + 10.905204772949219, + 8.877875328063965, + 3.9541573524475098, + 4.212759494781494, + 1.6434650421142578, + 6.014175891876221, + 11.81765079498291, + 5.7166290283203125, + 10.235824584960938, + 9.988836288452148, + 12.271153450012207, + 9.359338760375977, + 10.802840232849121, + 5.58129358291626, + 5.053167343139648, + 11.393550872802734, + 10.70861530303955, + 5.989745616912842 + ], + "xaxis": "x", + "y": [ + 10.656658172607422, + 11.18799877166748, + 10.856574058532715, + 11.44901180267334, + 11.629622459411621, + 10.141706466674805, + 10.326932907104492, + 11.163639068603516, + 8.577914237976074, + 11.202803611755371, + 11.431288719177246, + 8.897784233093262, + 10.777013778686523, + 8.757115364074707, + 10.037969589233398, + 10.378239631652832, + 11.159039497375488, + 8.996428489685059, + 10.407123565673828, + 11.209020614624023, + 10.958918571472168, + 8.058340072631836, + 11.037464141845703, + 11.1759614944458, + 11.124183654785156, + 10.990623474121094, + 11.050283432006836, + 11.445941925048828, + 11.252389907836914, + 8.858786582946777, + 11.310880661010742, + 10.045780181884766, + 10.47917652130127, + 10.691255569458008, + 10.799641609191895, + 10.416866302490234, + 10.8339204788208, + 10.151741981506348, + 11.161153793334961, + 10.8136568069458, + 11.20238208770752, + 10.2774019241333, + 9.544167518615723, + 10.656082153320312, + 8.673077583312988, + 8.642745018005371, + 10.137826919555664, + 9.065380096435547, + 11.219986915588379, + 8.91156005859375, + 10.705818176269531, + 9.090436935424805, + 9.463476181030273, + 11.382898330688477, + 10.480552673339844, + 10.009493827819824, + 9.64395523071289, + 10.823891639709473, + 9.20860481262207, + 9.860846519470215, + 11.427290916442871, + 9.020516395568848, + 11.223712921142578, + 10.761098861694336, + 11.18014907836914, + 9.849919319152832, + 10.834057807922363, + 11.583465576171875, + 11.404841423034668, + 11.484086990356445, + 9.392868041992188, + 10.26281452178955, + 8.97150707244873, + 10.296004295349121, + 9.24005126953125, + 11.328397750854492, + 10.45443058013916, + 10.914872169494629, + 11.203351020812988, + 11.531599998474121, + 10.612454414367676, + 10.716697692871094, + 10.481507301330566, + 10.04078483581543, + 11.038707733154297, + 11.00247573852539, + 10.200936317443848, + 11.360563278198242, + 10.730998039245605, + 11.422957420349121, + 11.127883911132812, + 11.43701457977295, + 11.410134315490723, + 11.13198184967041, + 11.285154342651367, + 11.345113754272461, + 11.341779708862305, + 11.284598350524902, + 11.175896644592285, + 9.614200592041016, + 10.498095512390137, + 10.6130952835083, + 10.969415664672852, + 11.455740928649902, + 11.138276100158691, + 10.079330444335938, + 10.939187049865723, + 11.090421676635742, + 11.015386581420898, + 11.12269115447998, + 10.387526512145996, + 11.13922119140625, + 10.118760108947754, + 10.968926429748535, + 8.20037841796875, + 9.487682342529297, + 11.161566734313965, + 11.150999069213867, + 11.20274829864502, + 11.388879776000977, + 8.734782218933105, + 11.375699043273926, + 11.217055320739746, + 10.880629539489746, + 10.830223083496094, + 11.652658462524414, + 9.245227813720703, + 9.567502975463867, + 10.921148300170898, + 11.088330268859863, + 11.289122581481934, + 10.845087051391602, + 11.407930374145508, + 8.506103515625, + 9.140424728393555, + 9.921130180358887, + 11.287715911865234, + 11.482010841369629, + 11.404109001159668, + 10.588408470153809, + 8.88369369506836, + 11.213618278503418, + 8.445860862731934, + 10.524004936218262, + 8.114008903503418, + 11.191266059875488, + 11.074394226074219, + 10.183130264282227, + 10.753056526184082, + 8.92208480834961, + 10.322144508361816, + 11.35918140411377, + 11.272193908691406, + 9.536117553710938, + 11.37548542022705, + 11.089654922485352, + 10.5033540725708, + 11.29785442352295, + 8.805676460266113, + 8.884170532226562, + 11.368857383728027 + ], + "yaxis": "y" + }, + { + "hovertemplate": "%{hovertext}

color=endoplasmic reticulum
x=%{x}
y=%{y}", + "hovertext": [ + "526_G11_1_6", + "526_G11_1_7", + "528_G11_1_13", + "545_G11_1_6", + "545_G11_1_11", + "545_G11_2_20", + "104_G6_1_9", + "104_G6_2_2", + "104_G6_2_3", + "104_G6_2_5", + "69_A5_2_13", + "91_A5_2_5", + "91_A5_2_7", + "1123_D6_2_4", + "172_G6_2_2", + "634_G1_3_19", + "639_G1_1_4", + "639_G1_3_7", + "1899_F12_31_6", + "1899_F12_32_5", + "1899_F12_32_14", + "1899_F12_32_15", + "2108_H7_2_11", + "2108_H7_2_13", + "703_F3_1_3", + "703_F3_1_9", + "703_F3_2_12", + "708_F3_1_12", + "708_F3_2_5", + "75_E10_1_7", + "758_E6_1_7", + "758_E6_2_4", + "133_H10_1_7", + "133_H10_2_4", + "125_F5_1_3", + "125_F5_1_5", + "165_F5_2_7", + "1894_H7_1_6", + "1894_H7_1_11", + "1894_H7_3_7", + "258_F9_1_4", + "258_F9_1_14", + "258_F9_2_14", + "259_F9_1_3", + "259_F9_2_9", + "260_F9_2_12", + "703_G8_1_9", + "703_G8_1_14", + "708_G8_1_11", + "121_G3_2_5", + "123_G3_1_7", + "123_G3_2_5", + "172_G3_1_1", + "172_G3_2_4", + "172_G3_2_9", + "641_A8_2_5", + "642_A8_3_12", + "105_H9_2_12", + "107_H9_2_12", + "160_H9_1_6", + "160_H9_2_6", + "160_H9_2_7", + "431_G4_4_12", + "437_G4_2_10", + "437_G4_2_13", + "102_C8_1_7", + "103_C8_1_7", + "103_C8_1_10", + "103_C8_2_7", + "48_C4_1_10", + "48_C4_2_7", + "48_C4_2_9", + "49_C4_2_4", + "47_H7_1_6", + "47_H7_2_4", + "48_H7_2_3", + "527_C9_1_2", + "527_C9_1_9", + "527_C9_3_3", + "529_C9_1_14", + "529_C9_2_4", + "529_C9_2_12", + "97_B12_1_7", + "99_B12_1_6", + "99_B12_2_5", + "99_B12_2_10", + "39_H9_1_5", + "40_H9_1_3", + "40_H9_1_7", + "40_H9_2_3" + ], + "legendgroup": "endoplasmic reticulum", + "marker": { + "color": "#1F77B4", + "symbol": "circle" + }, + "mode": "markers", + "name": "endoplasmic reticulum", + "showlegend": true, + "type": "scattergl", + "x": [ + 3.2447547912597656, + 2.9184703826904297, + 6.675962924957275, + 4.58085298538208, + -1.0868974924087524, + 6.6522064208984375, + 10.166969299316406, + 9.1868896484375, + 7.177773952484131, + 0.07045882940292358, + 0.5087297558784485, + -1.7863540649414062, + 2.561910629272461, + 3.6921324729919434, + 1.165887713432312, + 9.452832221984863, + 2.588078022003174, + 6.975824356079102, + 7.195924282073975, + 7.359630584716797, + -0.9562220573425293, + 10.40177059173584, + 7.1021881103515625, + 6.9552507400512695, + 2.0751757621765137, + 6.109659671783447, + 2.624572277069092, + 9.788066864013672, + 4.165965557098389, + 6.495021820068359, + 5.8981032371521, + 7.313299179077148, + 3.8879599571228027, + 4.181728363037109, + 5.148833751678467, + 8.433150291442871, + 6.64546012878418, + 2.234600305557251, + 7.133554935455322, + 4.738037109375, + 6.800375938415527, + 1.7412147521972656, + -0.16198743879795074, + 7.305188179016113, + 0.9637420773506165, + 6.039333343505859, + 2.10451078414917, + 5.706045627593994, + 7.4676666259765625, + 5.7704620361328125, + 5.598147392272949, + 12.167831420898438, + 6.628224849700928, + 12.493654251098633, + 7.512380123138428, + 3.445263385772705, + 9.66183090209961, + 10.436975479125977, + -2.3260107040405273, + 7.205696105957031, + 5.727794170379639, + 4.644102573394775, + 6.590414524078369, + -1.020456075668335, + 3.339718818664551, + 4.483584880828857, + -0.8045490980148315, + 7.671443462371826, + 6.833191394805908, + -0.8016027808189392, + 6.9017791748046875, + 1.6875706911087036, + 10.606059074401855, + 11.119812965393066, + 11.000222206115723, + 7.025929927825928, + 9.090808868408203, + 10.315220832824707, + 0.1049470603466034, + 2.0042178630828857, + 4.636541843414307, + 6.974136829376221, + 5.765756607055664, + 6.513875961303711, + 5.82921838760376, + 5.900471210479736, + 3.7823245525360107, + 5.430930137634277, + -1.0363892316818237, + 4.3196845054626465 + ], + "xaxis": "x", + "y": [ + 8.442706108093262, + 9.027788162231445, + 9.651198387145996, + 8.531401634216309, + 10.120037078857422, + 8.819242477416992, + 7.826521873474121, + 8.08011245727539, + 10.299992561340332, + 10.53884506225586, + 10.197161674499512, + 9.77857780456543, + 8.988310813903809, + 10.414640426635742, + 10.470003128051758, + 7.272236347198486, + 9.087246894836426, + 7.9927239418029785, + 8.651765823364258, + 8.609716415405273, + 9.450906753540039, + 7.840949058532715, + 7.715696334838867, + 9.010802268981934, + 9.602347373962402, + 9.627220153808594, + 9.391681671142578, + 7.2248215675354, + 10.258728981018066, + 9.362950325012207, + 10.932474136352539, + 8.300395011901855, + 11.1378173828125, + 10.225829124450684, + 9.795682907104492, + 9.14382266998291, + 10.311723709106445, + 9.63627815246582, + 8.74305534362793, + 8.529475212097168, + 8.071136474609375, + 10.601019859313965, + 9.991802215576172, + 8.099489212036133, + 10.052708625793457, + 9.553077697753906, + 8.6648530960083, + 10.508125305175781, + 8.963942527770996, + 8.433778762817383, + 9.670740127563477, + 7.404077529907227, + 9.773726463317871, + 7.455489635467529, + 7.711250305175781, + 8.129379272460938, + 7.819215297698975, + 7.184724807739258, + 9.752079963684082, + 10.223308563232422, + 9.038124084472656, + 8.581615447998047, + 9.555394172668457, + 9.305290222167969, + 7.915450096130371, + 7.984615325927734, + 9.927027702331543, + 8.158071517944336, + 9.223053932189941, + 10.197752952575684, + 9.995200157165527, + 8.632176399230957, + 7.646537780761719, + 8.76187515258789, + 9.245253562927246, + 7.906305313110352, + 7.745706558227539, + 7.1676530838012695, + 10.413887023925781, + 8.491966247558594, + 8.586467742919922, + 8.698546409606934, + 9.416695594787598, + 10.146071434020996, + 8.716909408569336, + 8.948263168334961, + 11.29104232788086, + 9.67658805847168, + 10.364914894104004, + 9.538354873657227 + ], + "yaxis": "y" + }, + { + "hovertemplate": "%{hovertext}

color=nucleoli
x=%{x}
y=%{y}", + "hovertext": [ + "505_C9_1_6", + "505_C9_1_7", + "505_C9_1_8", + "505_C9_1_11", + "505_C9_1_12", + "505_C9_1_17", + "611_F2_3_4", + "611_F2_3_13", + "614_F2_2_8", + "618_F2_1_3", + "618_F2_2_6", + "618_F2_2_10", + "618_F2_2_11", + "618_F2_2_12", + "149_G3_1_8", + "193_A10_1_6", + "193_A10_1_12", + "194_A10_1_7", + "194_A10_1_11", + "625_D11_2_4", + "625_D11_2_10", + "631_D11_1_12", + "631_D11_2_6", + "631_D11_2_7", + "631_D11_2_10", + "133_C9_1_5", + "133_C9_2_5", + "135_C9_1_2", + "1130_H2_1_7", + "1130_H2_2_9", + "408_C5_2_5", + "418_A1_3_7", + "418_A1_3_10", + "429_A1_1_4", + "429_A1_1_10", + "429_A1_2_9", + "125_D10_1_7", + "139_F7_1_4", + "139_F7_1_6", + "139_F7_2_4", + "166_F7_1_4", + "166_F7_2_3", + "604_G1_1_7", + "404_A11_1_5", + "404_A11_1_6", + "404_A11_2_14", + "407_A11_1_13", + "407_A11_2_1", + "410_A11_1_12", + "566_H6_1_2", + "566_H6_1_3", + "566_H6_1_9", + "566_H6_2_4", + "566_H6_2_12", + "569_H6_2_4", + "760_E2_1_2", + "775_E2_2_5", + "1238_B6_1_4", + "1238_B6_1_5", + "1238_B6_4_9", + "1244_E3_11_10", + "1247_E3_1_12", + "1888_H3_4_9", + "471_F10_1_4", + "471_F10_1_10", + "471_F10_1_14", + "471_F10_1_15", + "471_F10_3_2", + "471_F10_3_6", + "563_G6_2_7", + "566_G6_1_2", + "566_G6_1_4", + "566_G6_2_3", + "569_G6_2_4", + "569_G6_2_8", + "562_F7_2_5", + "568_F7_3_6", + "826_H5_2_6", + "826_H5_2_12", + "826_H5_2_25", + "102_G10_1_2", + "102_G10_2_5", + "103_G10_1_3", + "103_G10_2_5", + "104_G10_1_3", + "104_G10_2_4", + "164_F5_2_5", + "164_F5_2_8", + "271_E9_1_7", + "271_E9_1_8", + "271_E9_2_5", + "271_E9_2_6", + "271_E9_2_8", + "565_C8_2_13", + "584_C8_2_2", + "25_A4_2_3", + "8_H11_2_4", + "8_H11_2_6", + "9_H11_1_1" + ], + "legendgroup": "nucleoli", + "marker": { + "color": "#FF7F0E", + "symbol": "circle" + }, + "mode": "markers", + "name": "nucleoli", + "showlegend": true, + "type": "scattergl", + "x": [ + 5.307034015655518, + 2.2012832164764404, + 3.695546865463257, + 3.6101341247558594, + 1.461788296699524, + 9.236124038696289, + 11.36357593536377, + 9.595953941345215, + 10.810388565063477, + 6.967933654785156, + 1.1884777545928955, + 1.0686877965927124, + 10.806251525878906, + 11.62773323059082, + 5.347238063812256, + 10.020282745361328, + 6.667942047119141, + 2.8861379623413086, + 9.220315933227539, + 5.212121486663818, + 5.4374680519104, + 6.713620185852051, + 4.980778217315674, + 3.549438238143921, + 6.610246658325195, + 8.573939323425293, + 9.877596855163574, + 11.522047996520996, + 1.5197639465332031, + 9.431501388549805, + 3.790909767150879, + 4.915558815002441, + -0.6567122340202332, + 7.826632499694824, + 1.6202281713485718, + 3.883378505706787, + 4.524829387664795, + 6.727118492126465, + 5.17396354675293, + 9.509746551513672, + 4.345511436462402, + 6.862484455108643, + 8.076075553894043, + 12.017398834228516, + 11.5883150100708, + 10.452630043029785, + 12.184310913085938, + 9.42901611328125, + -2.6048970222473145, + 5.444400787353516, + 5.552539825439453, + 3.801384210586548, + 9.63230037689209, + 3.3852038383483887, + 6.489726543426514, + 9.621232986450195, + 8.090157508850098, + 4.335958957672119, + 4.369500160217285, + 1.279406189918518, + 11.12339973449707, + 7.733190059661865, + 1.9515550136566162, + 1.2634094953536987, + 5.534433364868164, + 1.2223244905471802, + 0.5669420957565308, + 2.8198423385620117, + 10.758598327636719, + 9.512332916259766, + 11.854557991027832, + 1.525325894355774, + 6.600167751312256, + 11.09082317352295, + 8.608187675476074, + 8.9635591506958, + 5.694570064544678, + 2.072519540786743, + 6.553169250488281, + 2.709378719329834, + 9.626931190490723, + 3.6699540615081787, + 2.6494152545928955, + 5.812168121337891, + 2.505359649658203, + 10.378584861755371, + 5.8731865882873535, + 6.859099388122559, + 8.902178764343262, + 5.629794120788574, + -1.1933177709579468, + 6.076942443847656, + 4.888103008270264, + 8.76037883758545, + 10.901312828063965, + 10.137368202209473, + 9.22635269165039, + 9.259835243225098, + 11.280158042907715 + ], + "xaxis": "x", + "y": [ + 9.244851112365723, + 10.878959655761719, + 9.761214256286621, + 10.530945777893066, + 10.286314964294434, + 8.066060066223145, + 8.247003555297852, + 8.3203125, + 8.704936981201172, + 10.619508743286133, + 9.815614700317383, + 10.640349388122559, + 8.436751365661621, + 8.253320693969727, + 11.284725189208984, + 8.928315162658691, + 10.915999412536621, + 8.759121894836426, + 10.159628868103027, + 10.664199829101562, + 10.879578590393066, + 10.970808029174805, + 11.35784912109375, + 10.67605972290039, + 11.115056037902832, + 11.173011779785156, + 11.12653636932373, + 9.768047332763672, + 8.648331642150879, + 10.625454902648926, + 11.09659481048584, + 10.471290588378906, + 10.419303894042969, + 8.075080871582031, + 10.125783920288086, + 10.628018379211426, + 10.871357917785645, + 8.696772575378418, + 10.890265464782715, + 8.535173416137695, + 10.959013938903809, + 11.07613468170166, + 10.215214729309082, + 9.453015327453613, + 9.507352828979492, + 10.44007682800293, + 7.352478504180908, + 8.652122497558594, + 9.82018756866455, + 11.668636322021484, + 10.88727855682373, + 10.603824615478516, + 8.86241340637207, + 10.89153003692627, + 11.37387752532959, + 8.725813865661621, + 11.068503379821777, + 10.882287979125977, + 10.832447052001953, + 10.601212501525879, + 9.199073791503906, + 11.37430477142334, + 9.04110336303711, + 10.707232475280762, + 9.185870170593262, + 9.98963737487793, + 10.276545524597168, + 10.54478931427002, + 8.804612159729004, + 10.927525520324707, + 8.342634201049805, + 10.017916679382324, + 10.329926490783691, + 8.797359466552734, + 9.27536678314209, + 11.196864128112793, + 11.560546875, + 10.281991004943848, + 10.979039192199707, + 10.183389663696289, + 11.088523864746094, + 10.5687837600708, + 11.071355819702148, + 9.999702453613281, + 11.04114818572998, + 11.075127601623535, + 10.975343704223633, + 11.287925720214844, + 9.765018463134766, + 11.249611854553223, + 10.445730209350586, + 10.891724586486816, + 10.867637634277344, + 8.29462718963623, + 8.901382446289062, + 8.75167179107666, + 10.403427124023438, + 10.524951934814453, + 10.532386779785156 + ], + "yaxis": "y" + }, + { + "hovertemplate": "%{hovertext}

color=nucleoli fibrillar center
x=%{x}
y=%{y}", + "hovertext": [ + "508_C9_1_20", + "604_G4_1_5", + "607_G4_1_2", + "609_G4_1_3", + "809_D1_7_6", + "826_D1_2_3", + "412_H6_1_4", + "412_H6_1_9", + "412_H6_2_4", + "419_H6_1_13", + "471_H6_2_6", + "471_H6_2_9", + "471_H6_2_11", + "471_H6_2_19", + "471_H6_2_20", + "651_H1_2_3", + "652_H1_1_3", + "652_H1_1_6", + "656_H1_1_6", + "484_E6_1_16", + "484_E6_2_3", + "484_E6_2_8", + "492_E6_2_4", + "450_F3_3_18", + "719_C10_1_4", + "719_C10_1_6", + "764_C10_1_9", + "1819_H5_18_cr59f6d6c051e08_3", + "1819_H5_18_cr59f6d6c051e08_5", + "1819_H5_4_cr59f6d6c0517f9_2", + "1819_H5_4_cr59f6d6c0517f9_11", + "402_D4_1_7", + "402_D4_1_11", + "402_D4_2_6", + "405_D4_2_1", + "405_D4_2_10", + "409_D4_1_1", + "409_D4_1_2", + "409_D4_2_6", + "575_F7_3_9", + "412_G12_2_7", + "419_G12_1_10", + "419_G12_2_20", + "471_G12_1_6", + "471_G12_1_9", + "471_G12_2_12", + "433_G10_2_5", + "569_A11_1_8", + "569_A11_1_9", + "569_A11_1_11", + "569_A11_2_7", + "569_A11_2_13", + "107_A12_1_1", + "107_A12_2_7", + "160_A12_1_4", + "667_C2_2_3", + "144_D11_1_8", + "144_D11_1_9", + "144_D11_2_1", + "144_D11_2_4", + "144_D11_2_5", + "23_A4_1_4", + "23_A4_1_6", + "24_A4_1_8" + ], + "legendgroup": "nucleoli fibrillar center", + "marker": { + "color": "#2CA02C", + "symbol": "circle" + }, + "mode": "markers", + "name": "nucleoli fibrillar center", + "showlegend": true, + "type": "scattergl", + "x": [ + 9.63404655456543, + 10.623029708862305, + 11.051946640014648, + 5.4707136154174805, + 11.429574012756348, + 8.150724411010742, + 11.569767951965332, + 4.19325590133667, + 8.06488037109375, + 4.503082752227783, + 4.740408420562744, + 11.424897193908691, + 9.302176475524902, + 9.996280670166016, + 5.498170852661133, + -0.436117559671402, + 3.343661069869995, + 5.027379512786865, + 5.042795658111572, + 6.086508274078369, + 1.870320439338684, + 6.934201240539551, + 1.764568567276001, + 0.3037610948085785, + 10.077630996704102, + 7.841760635375977, + 12.265966415405273, + 6.817480087280273, + 8.173808097839355, + -2.085279703140259, + 8.39441967010498, + 5.0827317237854, + 6.781822681427002, + 7.6320929527282715, + 7.145431995391846, + 10.284096717834473, + 9.923644065856934, + 9.991412162780762, + 4.430325508117676, + 3.53239107131958, + 5.792045593261719, + 6.870211124420166, + 10.796614646911621, + 4.3686747550964355, + 11.795412063598633, + 6.2166852951049805, + 9.399028778076172, + 7.375187397003174, + 8.840457916259766, + 9.058771133422852, + 10.830655097961426, + 9.974544525146484, + 8.529434204101562, + 2.6785807609558105, + 8.5991849899292, + 6.159163475036621, + 8.064249992370605, + 8.946314811706543, + 10.865194320678711, + 9.544049263000488, + 6.6627936363220215, + 1.579803705215454, + 8.143524169921875, + 6.509160995483398 + ], + "xaxis": "x", + "y": [ + 8.728517532348633, + 8.770746231079102, + 9.11677074432373, + 10.373712539672852, + 9.237421035766602, + 10.26722240447998, + 9.459091186523438, + 11.120741844177246, + 10.939696311950684, + 10.99378776550293, + 9.126243591308594, + 9.010720252990723, + 9.425095558166504, + 8.716423988342285, + 11.614737510681152, + 10.041977882385254, + 10.406542778015137, + 8.059087753295898, + 9.730274200439453, + 10.465351104736328, + 10.812211036682129, + 8.875877380371094, + 10.020499229431152, + 9.797350883483887, + 11.307815551757812, + 11.42276668548584, + 9.355462074279785, + 11.096529006958008, + 9.877994537353516, + 10.070211410522461, + 10.313555717468262, + 9.577936172485352, + 10.8699369430542, + 7.856522083282471, + 11.355256080627441, + 8.103291511535645, + 9.05104923248291, + 8.869852066040039, + 11.032855033874512, + 11.107758522033691, + 11.58067798614502, + 11.130584716796875, + 10.526925086975098, + 10.857772827148438, + 8.913965225219727, + 10.759373664855957, + 11.272541999816895, + 11.442437171936035, + 11.108317375183105, + 10.335371017456055, + 10.842527389526367, + 11.220292091369629, + 10.026568412780762, + 9.58740520477295, + 10.71532917022705, + 11.01673698425293, + 11.337628364562988, + 10.21743106842041, + 11.06139087677002, + 10.9774169921875, + 11.031354904174805, + 10.851637840270996, + 10.097594261169434, + 8.745205879211426 + ], + "yaxis": "y" + }, + { + "hovertemplate": "%{hovertext}

color=centrosome
x=%{x}
y=%{y}", + "hovertext": [ + "411_C12_3_4", + "411_C12_3_13", + "415_C12_1_12", + "415_C12_2_2", + "416_C12_1_5", + "416_C12_1_6", + "416_C12_2_12", + "1894_E3_1_4", + "776_B2_1_7", + "776_B2_1_8", + "776_B2_1_13", + "776_B2_5_9", + "776_B2_5_14", + "899_B2_1_8", + "899_B2_1_11", + "899_B2_2_2", + "2108_C12_5_5", + "640_A4_1_10", + "640_A4_1_13", + "640_A4_2_6", + "641_A4_3_9", + "641_A4_3_10", + "642_A4_1_4", + "642_A4_1_7", + "642_A4_8_10", + "430_H4_2_6", + "432_H4_3_4", + "432_H4_3_10", + "516_D8_3_10", + "519_D8_4_12", + "556_D8_2_10", + "295_G10_1_7", + "295_G10_2_7", + "295_G10_2_11", + "296_G10_1_7", + "296_G10_2_6", + "297_G10_1_6", + "221_F4_1_9", + "221_F4_2_5", + "793_E4_4_2", + "793_E4_4_9", + "793_E4_5_11", + "845_E4_9_13", + "281_B11_2_14", + "757_B3_1_10", + "757_B3_1_14", + "757_B3_1_15", + "757_B3_2_7", + "757_B3_2_18", + "761_B3_3_7", + "769_B3_1_13", + "769_B3_2_9", + "769_B3_2_12", + "899_E11_2_5", + "112_C8_1_7", + "112_C8_2_8", + "272_D7_1_3", + "272_D7_1_9", + "272_D7_2_14" + ], + "legendgroup": "centrosome", + "marker": { + "color": "#D62728", + "symbol": "circle" + }, + "mode": "markers", + "name": "centrosome", + "showlegend": true, + "type": "scattergl", + "x": [ + 2.7901041507720947, + 0.874021589756012, + 8.516054153442383, + 4.91292142868042, + 3.4567291736602783, + 8.75755500793457, + 1.0790436267852783, + 2.26839017868042, + 4.938488483428955, + 4.133512496948242, + -1.1102535724639893, + 3.5276830196380615, + -0.062249138951301575, + -1.182023048400879, + 0.06675713509321213, + 4.64168643951416, + 0.20393314957618713, + 3.788853645324707, + 4.931152820587158, + 5.1454691886901855, + 8.186762809753418, + 10.12415885925293, + -0.6874390840530396, + 8.80850601196289, + 5.936117649078369, + 5.048835754394531, + 10.392433166503906, + 5.0215301513671875, + 11.059093475341797, + 10.99343204498291, + 4.884566783905029, + -1.0599615573883057, + 0.19211210310459137, + -0.5866060853004456, + 10.631531715393066, + 9.003168106079102, + 10.223150253295898, + -1.362527847290039, + 7.4354023933410645, + 7.5122575759887695, + 0.648832380771637, + 8.46804428100586, + -1.2074236869812012, + 3.4489316940307617, + -0.6104220151901245, + 8.035677909851074, + 9.761763572692871, + -0.7032376527786255, + 8.075616836547852, + 7.090182304382324, + -0.9466643929481506, + 1.9249573945999146, + 2.074481964111328, + 0.16279160976409912, + 4.347227573394775, + 6.874521732330322, + 1.7617931365966797, + 3.709890365600586, + 5.1867265701293945 + ], + "xaxis": "x", + "y": [ + 8.645325660705566, + 9.793307304382324, + 7.825340747833252, + 8.018016815185547, + 9.249263763427734, + 8.013355255126953, + 10.445072174072266, + 8.802763938903809, + 8.352803230285645, + 9.570595741271973, + 9.865367889404297, + 9.495330810546875, + 10.131620407104492, + 9.84259033203125, + 10.330339431762695, + 8.959420204162598, + 10.025975227355957, + 9.848116874694824, + 7.829399585723877, + 8.102012634277344, + 9.685673713684082, + 7.893614292144775, + 10.3157377243042, + 7.569931983947754, + 8.61505126953125, + 7.848857402801514, + 7.6770501136779785, + 9.221097946166992, + 8.731588363647461, + 8.437544822692871, + 10.487589836120605, + 10.419699668884277, + 10.257688522338867, + 10.455410957336426, + 8.810300827026367, + 7.993426322937012, + 7.692299842834473, + 10.144434928894043, + 7.731456279754639, + 7.934937477111816, + 9.484111785888672, + 7.73773193359375, + 9.814582824707031, + 10.187749862670898, + 9.57421875, + 8.025893211364746, + 11.124062538146973, + 9.917794227600098, + 8.237277030944824, + 10.387939453125, + 9.82646369934082, + 8.636519432067871, + 8.639321327209473, + 9.622383117675781, + 10.104522705078125, + 8.529229164123535, + 10.113980293273926, + 11.294637680053711, + 8.061869621276855 + ], + "yaxis": "y" + }, + { + "hovertemplate": "%{hovertext}

color=nuclear bodies
x=%{x}
y=%{y}", + "hovertext": [ + "431_C1_2_4", + "431_C1_2_13", + "437_C1_3_2", + "437_C1_3_6", + "342_D6_1_7", + "819_E10_2_5", + "819_E10_2_10", + "826_E10_2_7", + "826_E10_2_12", + "834_A3_1_11", + "834_A3_2_8", + "856_F5_1_1", + "856_F5_1_7", + "271_H4_2_10", + "583_A12_2_5", + "598_A12_1_5", + "598_A12_2_6", + "602_A12_1_2", + "542_B4_1_9", + "542_B4_2_6", + "542_B4_2_12", + "544_B4_1_17", + "544_B4_2_15", + "571_B4_1_2", + "571_B4_2_8", + "523_A9_1_4", + "382_C11_2_14", + "397_C11_3_4", + "62_D3_2_8", + "93_D3_1_4", + "93_D3_2_6", + "303_D8_1_6", + "303_D8_5_3", + "921_B10_4_8", + "923_B10_1_7", + "923_B10_2_9", + "931_B10_2_5", + "931_B10_2_7", + "944_G11_1_3", + "944_G11_1_9", + "947_G11_2_9", + "899_H7_1_8", + "899_H7_2_6", + "183_C1_1_13", + "183_C1_2_12", + "185_C1_1_5", + "185_C1_1_8", + "185_C1_2_7", + "242_C1_2_9", + "793_H11_2_4", + "793_H11_2_9", + "793_H11_2_15", + "800_H11_2_6", + "257_H11_2_2", + "257_H11_2_6", + "257_H11_2_7", + "190_A4_1_4", + "190_A4_1_16", + "43_H10_2_3", + "468_F1_2_11", + "468_F1_2_24", + "468_F1_3_4", + "468_F1_3_6", + "468_F1_3_13", + "473_F1_4_4", + "473_F1_4_6", + "63_A1_1_12", + "59_G9_2_2", + "59_G9_2_10", + "59_G9_2_11", + "61_G9_2_7" + ], + "legendgroup": "nuclear bodies", + "marker": { + "color": "#9467BD", + "symbol": "circle" + }, + "mode": "markers", + "name": "nuclear bodies", + "showlegend": true, + "type": "scattergl", + "x": [ + 6.819511413574219, + -1.4979426860809326, + 6.714967727661133, + 10.15843391418457, + 10.982986450195312, + 7.552469730377197, + 4.405271530151367, + 2.126934289932251, + 8.636754035949707, + 1.006922960281372, + 1.1956366300582886, + 7.551413059234619, + 10.815673828125, + 1.11006760597229, + 9.769607543945312, + 10.736916542053223, + 8.468669891357422, + 0.8967605233192444, + 6.415238380432129, + -0.9088709354400635, + 11.284372329711914, + 2.919825315475464, + 2.955153465270996, + 8.658828735351562, + 5.957705974578857, + 11.767608642578125, + 3.0754308700561523, + 1.5623754262924194, + 6.832459449768066, + 3.9559390544891357, + 0.9555965662002563, + 12.151062965393066, + 9.497099876403809, + 6.656554698944092, + 6.161899566650391, + 3.4917244911193848, + 5.035382270812988, + 3.5406405925750732, + -0.9288233518600464, + 1.7397575378417969, + 9.492664337158203, + 12.072614669799805, + 4.4753594398498535, + 3.9321999549865723, + -0.5812107920646667, + 4.828789710998535, + 4.343875885009766, + 11.386178970336914, + 8.141541481018066, + 10.042104721069336, + 4.3508195877075195, + 4.860072135925293, + 8.342652320861816, + 11.919004440307617, + 9.743101119995117, + 9.722825050354004, + 10.064361572265625, + 7.599611759185791, + 10.777355194091797, + 1.8635993003845215, + 8.719890594482422, + 5.87231969833374, + 5.513407230377197, + 10.214001655578613, + 6.5034894943237305, + 10.840243339538574, + 7.310122013092041, + 4.905212879180908, + 11.744564056396484, + 4.0952301025390625, + 8.75599193572998 + ], + "xaxis": "x", + "y": [ + 10.629657745361328, + 10.146395683288574, + 8.651334762573242, + 8.756651878356934, + 7.739330291748047, + 10.792143821716309, + 10.731819152832031, + 10.688824653625488, + 8.159095764160156, + 10.685564041137695, + 10.630619049072266, + 11.45444393157959, + 8.790694236755371, + 10.140074729919434, + 8.752631187438965, + 8.812499046325684, + 10.804101943969727, + 10.397555351257324, + 10.586000442504883, + 9.837142944335938, + 8.470909118652344, + 9.103888511657715, + 11.081582069396973, + 11.222649574279785, + 10.667492866516113, + 8.563155174255371, + 11.150300025939941, + 9.27762222290039, + 11.046632766723633, + 11.409279823303223, + 10.576905250549316, + 9.23837661743164, + 8.175479888916016, + 10.968902587890625, + 11.544849395751953, + 10.460160255432129, + 9.973970413208008, + 10.823179244995117, + 9.820189476013184, + 10.238256454467773, + 10.967262268066406, + 9.555807113647461, + 11.607996940612793, + 11.208882331848145, + 9.512435913085938, + 9.685239791870117, + 11.44731330871582, + 8.822015762329102, + 8.301166534423828, + 8.850359916687012, + 11.318218231201172, + 11.320903778076172, + 10.360037803649902, + 9.096857070922852, + 11.328948974609375, + 11.357829093933105, + 8.985947608947754, + 8.007308006286621, + 10.893662452697754, + 8.737584114074707, + 8.287182807922363, + 11.084639549255371, + 11.536459922790527, + 8.904513359069824, + 10.56672191619873, + 8.668307304382324, + 10.88458251953125, + 10.676512718200684, + 8.298717498779297, + 11.429472923278809, + 10.341072082519531 + ], + "yaxis": "y" + }, + { + "hovertemplate": "%{hovertext}

color=nuclear membrane
x=%{x}
y=%{y}", + "hovertext": [ + "467_A7_1_5", + "467_A7_1_10", + "467_A7_1_13", + "415_E12_1_8", + "415_E12_2_2", + "416_E12_1_13", + "416_E12_1_17", + "404_B8_1_16", + "404_B8_2_9", + "407_B8_2_5", + "410_B8_1_11", + "410_B8_2_6", + "667_G6_1_8", + "667_G6_1_9", + "673_G6_1_7", + "673_G6_2_5", + "138_D12_1_7", + "166_D12_2_7", + "1238_C5_4_1", + "537_D5_2_10", + "553_D5_3_13", + "537_G2_1_5", + "540_G2_1_9", + "553_G2_1_6", + "553_G2_1_12", + "553_G2_2_12", + "553_G2_2_13", + "553_G2_2_15", + "21_C1_1_9", + "21_C1_1_10", + "22_C1_1_7", + "22_C1_1_11", + "21_H8_1_4", + "22_H8_2_5", + "20_B6_1_6", + "20_B6_1_7", + "20_B6_1_10", + "20_B6_2_4", + "20_B6_2_5", + "21_B6_1_3", + "22_B6_1_7", + "921_H7_4_9", + "923_H7_1_4", + "923_H7_1_10", + "923_H7_2_9", + "923_H7_2_12", + "931_H7_1_9", + "931_H7_1_11", + "931_H7_2_7", + "128_D5_1_16", + "128_D5_2_7", + "128_D5_2_13", + "941_G2_1_19", + "941_G2_2_1", + "941_G2_2_9", + "941_G2_2_11", + "221_G2_1_12", + "222_G2_1_9", + "301_F9_2_3", + "301_F9_2_7", + "342_F9_1_15", + "342_F9_2_12", + "394_C4_3_6", + "395_C4_1_10", + "395_C4_2_10", + "399_C4_1_7", + "399_C4_2_14" + ], + "legendgroup": "nuclear membrane", + "marker": { + "color": "#8C564B", + "symbol": "circle" + }, + "mode": "markers", + "name": "nuclear membrane", + "showlegend": true, + "type": "scattergl", + "x": [ + 1.0769258737564087, + 2.237205743789673, + 9.433876991271973, + 4.117219924926758, + 6.688831329345703, + 6.604763507843018, + 6.010365009307861, + 8.741850852966309, + 8.509101867675781, + 8.97963809967041, + 4.7867841720581055, + 6.898395538330078, + 7.27094030380249, + 7.097294330596924, + 9.740988731384277, + 10.915390014648438, + 11.7156982421875, + 9.941634178161621, + 8.420010566711426, + 10.18942642211914, + 9.063899040222168, + 10.582364082336426, + 11.727190971374512, + 8.810046195983887, + 7.337896823883057, + 8.899704933166504, + 11.956937789916992, + 12.200250625610352, + 6.004112243652344, + 9.070633888244629, + 11.657057762145996, + 9.922208786010742, + 3.3051559925079346, + 12.087153434753418, + 9.383602142333984, + 11.964122772216797, + 11.915939331054688, + 12.08342170715332, + 11.990525245666504, + 11.016666412353516, + 10.979894638061523, + 3.7488372325897217, + 10.433528900146484, + 5.1994476318359375, + 10.87480354309082, + 9.497450828552246, + 11.85411262512207, + 10.445111274719238, + 9.30124282836914, + 10.46670150756836, + 2.272946834564209, + 1.939941167831421, + 4.396029472351074, + 9.95264720916748, + 6.2784647941589355, + 6.419801235198975, + 6.8658833503723145, + -1.2691106796264648, + 10.32048511505127, + 9.00498104095459, + 11.381840705871582, + 8.532036781311035, + 5.76626443862915, + 11.59349250793457, + 8.627525329589844, + 5.37863302230835, + 8.305771827697754 + ], + "xaxis": "x", + "y": [ + 10.102506637573242, + 9.855990409851074, + 8.112512588500977, + 11.296436309814453, + 8.993387222290039, + 11.139505386352539, + 10.17568588256836, + 10.103300094604492, + 10.111981391906738, + 10.214899063110352, + 11.716001510620117, + 10.318156242370605, + 10.286762237548828, + 10.416485786437988, + 11.013307571411133, + 9.198184967041016, + 8.997018814086914, + 9.319828033447266, + 10.56431770324707, + 8.994754791259766, + 11.292583465576172, + 11.166237831115723, + 9.97184944152832, + 11.34990406036377, + 10.774373054504395, + 11.232536315917969, + 9.70197582244873, + 9.620158195495605, + 10.159516334533691, + 9.69892692565918, + 9.243497848510742, + 11.13408374786377, + 10.926000595092773, + 9.092622756958008, + 11.390235900878906, + 10.05028247833252, + 9.937987327575684, + 9.58938217163086, + 10.022841453552246, + 9.076828002929688, + 11.074780464172363, + 11.146273612976074, + 10.97065258026123, + 9.496623992919922, + 10.808326721191406, + 11.136975288391113, + 9.320352554321289, + 11.125741004943848, + 10.522614479064941, + 8.793424606323242, + 9.51340103149414, + 9.735759735107422, + 8.818861961364746, + 11.051640510559082, + 10.781630516052246, + 10.206765174865723, + 10.483786582946777, + 10.358619689941406, + 11.162014961242676, + 11.259156227111816, + 9.488568305969238, + 11.417133331298828, + 10.57093334197998, + 9.118217468261719, + 11.465967178344727, + 9.97842788696289, + 10.458306312561035 + ], + "yaxis": "y" + }, + { + "hovertemplate": "%{hovertext}

color=projection
x=%{x}
y=%{y}", + "hovertext": [ + "104_F7_2_7", + "104_F7_2_9", + "104_F7_2_11", + "405_E10_1_10", + "405_E10_2_15", + "35_G12_2_3", + "404_A2_1_11", + "407_A2_1_5", + "407_A2_2_7", + "410_A2_2_10", + "69_F1_1_10", + "508_H6_2_20", + "754_E6_4_12", + "813_E6_2_4", + "534_B3_2_10", + "297_E11_1_4", + "297_E11_2_14", + "15_G10_2_5", + "79_F2_1_3", + "631_E12_1_8", + "631_E12_1_11", + "180_F10_1_9", + "180_F10_2_10", + "180_F10_2_11", + "181_F10_2_9", + "573_H11_4_4", + "589_H11_1_10", + "60_C7_1_7", + "976_H6_2_15", + "1200_E11_3_6", + "1200_E11_4_16", + "1773_E11_7_8", + "5_A9_2_4", + "5_A9_2_5", + "754_B4_1_11", + "486_F5_2_13", + "919_G7_2_5", + "340_G1_2_12", + "281_C8_2_9", + "282_C8_2_10", + "508_C1_1_7", + "548_C1_1_1", + "548_C1_2_7", + "611_C11_2_11", + "539_A7_3_13", + "652_C9_1_3", + "652_C9_1_11", + "4_H11_2_6", + "1029_E3_3_8", + "440_B6_4_7", + "860_B7_2_12", + "995_E3_2_21", + "241_G1_2_10" + ], + "legendgroup": "projection", + "marker": { + "color": "#E377C2", + "symbol": "circle" + }, + "mode": "markers", + "name": "projection", + "showlegend": true, + "type": "scattergl", + "x": [ + 2.8220162391662598, + 2.0947189331054688, + 2.3121414184570312, + 2.8480238914489746, + 2.948303699493408, + 2.929265022277832, + 3.200376510620117, + 2.261394500732422, + 3.048814296722412, + 2.8830606937408447, + 3.2838196754455566, + 2.3122212886810303, + 2.1796975135803223, + 2.859790086746216, + 2.1846511363983154, + 3.115233898162842, + 2.902467966079712, + 2.08587646484375, + 2.8921871185302734, + 3.0978469848632812, + 2.852065086364746, + 2.884228467941284, + 2.811978340148926, + 2.277855157852173, + 2.2817561626434326, + 2.3404386043548584, + 3.1054906845092773, + 2.3040971755981445, + 3.0140440464019775, + 2.869255781173706, + 2.2352135181427, + 2.8557589054107666, + 3.077486991882324, + 2.17663311958313, + 3.104400396347046, + 2.2963125705718994, + 3.081216812133789, + 2.3057804107666016, + 2.260838508605957, + 2.975579023361206, + 2.3348441123962402, + 2.8215689659118652, + 1.9969513416290283, + 2.343165397644043, + 2.137347936630249, + 2.0755209922790527, + 2.8152575492858887, + 2.1925032138824463, + 2.071396827697754, + 3.305856227874756, + 3.2211828231811523, + 2.200394868850708, + 2.300123453140259 + ], + "xaxis": "x", + "y": [ + 5.458002090454102, + 3.743023157119751, + 4.0146098136901855, + 5.511794090270996, + 5.641158580780029, + 5.611515522003174, + 5.975940227508545, + 3.944054365158081, + 5.772857666015625, + 5.553534030914307, + 6.083166599273682, + 3.989496946334839, + 3.8444862365722656, + 5.520756721496582, + 3.8507704734802246, + 5.894106864929199, + 5.583302021026611, + 3.735743522644043, + 5.571044445037842, + 5.861843109130859, + 5.519857406616211, + 5.572367191314697, + 5.430802822113037, + 3.9519436359405518, + 3.9599406719207764, + 4.0327677726745605, + 5.9076056480407715, + 4.012452602386475, + 5.761802673339844, + 5.543085098266602, + 3.912943124771118, + 5.50507926940918, + 5.856686592102051, + 3.85214900970459, + 5.865994930267334, + 3.980992078781128, + 5.82665491104126, + 3.943847179412842, + 3.9366719722747803, + 5.698197364807129, + 4.017688274383545, + 5.4394001960754395, + 3.6312921047210693, + 4.026690483093262, + 3.795874834060669, + 3.719465732574463, + 5.5655670166015625, + 3.856088876724243, + 3.7189526557922363, + 6.131337642669678, + 6.014585971832275, + 3.865633010864258, + 3.981794595718384 + ], + "yaxis": "y" + }, + { + "hovertemplate": "%{hovertext}

color=soma
x=%{x}
y=%{y}", + "hovertext": [ + "104_F7_2_10", + "409_E10_1_9", + "36_G12_2_3", + "821_B5_1_7", + "323_B9_2_6", + "326_B9_1_1", + "404_A2_2_7", + "68_F1_2_8", + "69_F1_2_13", + "69_F1_2_16", + "505_H6_1_16", + "505_H6_2_16", + "508_H6_1_11", + "508_H6_2_13", + "534_B3_2_4", + "534_B3_2_7", + "552_B3_1_9", + "552_B3_2_3", + "295_E11_1_6", + "81_F2_1_8", + "631_E12_2_14", + "180_F10_1_12", + "182_F10_1_14", + "589_H11_3_3", + "589_H11_3_9", + "709_H11_1_13", + "709_H11_1_14", + "60_C7_2_6", + "60_C7_2_10", + "1200_E11_4_17", + "132_D9_2_7", + "164_D9_1_9", + "1773_E11_7_1", + "754_B4_2_2", + "754_B4_2_6", + "486_F5_1_18", + "486_F5_2_7", + "490_F5_1_2", + "509_F5_1_16", + "509_F5_2_14", + "281_C8_2_14", + "282_C8_1_8", + "283_C8_1_9", + "283_C8_1_12", + "283_C8_1_13", + "548_C1_1_12", + "611_C11_2_14", + "614_C11_2_20", + "614_C11_3_3", + "552_A7_1_15", + "552_A7_2_13", + "652_C9_1_6", + "652_C9_2_10", + "656_C9_1_8", + "656_C9_3_5", + "4_H11_1_12", + "5_H11_1_6", + "1029_E3_2_3", + "440_B6_4_12", + "995_E3_1_9", + "995_E3_1_22", + "239_G1_2_9", + "240_G1_1_6" + ], + "legendgroup": "soma", + "marker": { + "color": "#7F7F7F", + "symbol": "circle" + }, + "mode": "markers", + "name": "soma", + "showlegend": true, + "type": "scattergl", + "x": [ + 12.474445343017578, + 12.09831428527832, + 12.642560958862305, + 10.95546817779541, + 12.414810180664062, + 12.076699256896973, + 11.15566635131836, + 10.332839965820312, + 12.562597274780273, + 12.524617195129395, + 12.537501335144043, + 12.170047760009766, + 12.449235916137695, + 12.616276741027832, + 12.673643112182617, + 12.601920127868652, + 12.379720687866211, + 12.40365982055664, + 12.056737899780273, + 10.576614379882812, + 12.267507553100586, + 12.462041854858398, + 11.588532447814941, + 12.553101539611816, + 12.273642539978027, + 12.122650146484375, + 12.066981315612793, + 10.917231559753418, + 12.100083351135254, + 12.416119575500488, + 10.22594165802002, + 12.299478530883789, + 12.235836029052734, + 10.495287895202637, + 12.492718696594238, + 12.490102767944336, + 10.913877487182617, + 12.634825706481934, + 12.524006843566895, + 10.658376693725586, + 12.328232765197754, + 12.601805686950684, + 12.582365036010742, + 12.494547843933105, + 9.013721466064453, + 12.502442359924316, + 12.568574905395508, + 12.455399513244629, + 11.968633651733398, + 12.454617500305176, + 12.569085121154785, + 12.485071182250977, + 12.577160835266113, + 12.066126823425293, + 12.032176971435547, + 12.53004264831543, + 11.91303825378418, + 11.896278381347656, + 12.415629386901855, + 12.121464729309082, + 11.814303398132324, + 11.309557914733887, + 12.111992835998535 + ], + "xaxis": "x", + "y": [ + 7.992579936981201, + 7.627177715301514, + 8.075285911560059, + 7.644983768463135, + 8.492981910705566, + 7.4276251792907715, + 7.422135829925537, + 7.232869625091553, + 8.073230743408203, + 7.977344036102295, + 8.160232543945312, + 7.399251937866211, + 7.4225687980651855, + 8.045621871948242, + 8.062952995300293, + 8.046911239624023, + 7.538491725921631, + 8.014336585998535, + 7.361385345458984, + 7.252135276794434, + 7.477945327758789, + 7.532444000244141, + 7.360785961151123, + 7.52120304107666, + 7.8504133224487305, + 7.319384574890137, + 7.388730049133301, + 8.645696640014648, + 7.906698226928711, + 7.625060081481934, + 7.162146091461182, + 7.628461837768555, + 8.372908592224121, + 7.679476737976074, + 7.441669940948486, + 7.609679698944092, + 7.43280029296875, + 7.8058366775512695, + 7.4821977615356445, + 7.30881404876709, + 7.943845272064209, + 8.101383209228516, + 7.521078586578369, + 7.666330814361572, + 8.034710884094238, + 8.158194541931152, + 7.965065002441406, + 8.304408073425293, + 8.16307258605957, + 7.495872974395752, + 7.706000328063965, + 8.158994674682617, + 7.574252605438232, + 7.6840128898620605, + 7.537256717681885, + 7.680474758148193, + 7.614544868469238, + 8.027783393859863, + 7.516109943389893, + 7.409327030181885, + 7.805696487426758, + 7.7866339683532715, + 7.521279811859131 + ], + "yaxis": "y" + } + ], + "layout": { + "legend": { + "title": { + "text": "color" + }, + "tracegroupgap": 0 + }, + "margin": { + "t": 60 + }, + "template": { + "data": { + "bar": [ + { + "error_x": { + "color": "rgb(36,36,36)" + }, + "error_y": { + "color": "rgb(36,36,36)" + }, + "marker": { + "line": { + "color": "white", + "width": 0.5 + }, + "pattern": { + "fillmode": "overlay", + "size": 10, + "solidity": 0.2 + } + }, + "type": "bar" + } + ], + "barpolar": [ + { + "marker": { + "line": { + "color": "white", + "width": 0.5 + }, + "pattern": { + "fillmode": "overlay", + "size": 10, + "solidity": 0.2 + } + }, + "type": "barpolar" + } + ], + "carpet": [ + { + "aaxis": { + "endlinecolor": "rgb(36,36,36)", + "gridcolor": "white", + "linecolor": "white", + "minorgridcolor": "white", + "startlinecolor": "rgb(36,36,36)" + }, + "baxis": { + "endlinecolor": "rgb(36,36,36)", + "gridcolor": "white", + "linecolor": "white", + "minorgridcolor": "white", + "startlinecolor": "rgb(36,36,36)" + }, + "type": "carpet" + } + ], + "choropleth": [ + { + "colorbar": { + "outlinewidth": 1, + "tickcolor": "rgb(36,36,36)", + "ticks": "outside" + }, + "type": "choropleth" + } + ], + "contour": [ + { + "colorbar": { + "outlinewidth": 1, + "tickcolor": "rgb(36,36,36)", + "ticks": "outside" + }, + "colorscale": [ + [ + 0, + "#440154" + ], + [ + 0.1111111111111111, + "#482878" + ], + [ + 0.2222222222222222, + "#3e4989" + ], + [ + 0.3333333333333333, + "#31688e" + ], + [ + 0.4444444444444444, + "#26828e" + ], + [ + 0.5555555555555556, + "#1f9e89" + ], + [ + 0.6666666666666666, + "#35b779" + ], + [ + 0.7777777777777778, + "#6ece58" + ], + [ + 0.8888888888888888, + "#b5de2b" + ], + [ + 1, + "#fde725" + ] + ], + "type": "contour" + } + ], + "contourcarpet": [ + { + "colorbar": { + "outlinewidth": 1, + "tickcolor": "rgb(36,36,36)", + "ticks": "outside" + }, + "type": "contourcarpet" + } + ], + "heatmap": [ + { + "colorbar": { + "outlinewidth": 1, + "tickcolor": "rgb(36,36,36)", + "ticks": "outside" + }, + "colorscale": [ + [ + 0, + "#440154" + ], + [ + 0.1111111111111111, + "#482878" + ], + [ + 0.2222222222222222, + "#3e4989" + ], + [ + 0.3333333333333333, + "#31688e" + ], + [ + 0.4444444444444444, + "#26828e" + ], + [ + 0.5555555555555556, + "#1f9e89" + ], + [ + 0.6666666666666666, + "#35b779" + ], + [ + 0.7777777777777778, + "#6ece58" + ], + [ + 0.8888888888888888, + "#b5de2b" + ], + [ + 1, + "#fde725" + ] + ], + "type": "heatmap" + } + ], + "heatmapgl": [ + { + "colorbar": { + "outlinewidth": 1, + "tickcolor": "rgb(36,36,36)", + "ticks": "outside" + }, + "colorscale": [ + [ + 0, + "#440154" + ], + [ + 0.1111111111111111, + "#482878" + ], + [ + 0.2222222222222222, + "#3e4989" + ], + [ + 0.3333333333333333, + "#31688e" + ], + [ + 0.4444444444444444, + "#26828e" + ], + [ + 0.5555555555555556, + "#1f9e89" + ], + [ + 0.6666666666666666, + "#35b779" + ], + [ + 0.7777777777777778, + "#6ece58" + ], + [ + 0.8888888888888888, + "#b5de2b" + ], + [ + 1, + "#fde725" + ] + ], + "type": "heatmapgl" + } + ], + "histogram": [ + { + "marker": { + "line": { + "color": "white", + "width": 0.6 + } + }, + "type": "histogram" + } + ], + "histogram2d": [ + { + "colorbar": { + "outlinewidth": 1, + "tickcolor": "rgb(36,36,36)", + "ticks": "outside" + }, + "colorscale": [ + [ + 0, + "#440154" + ], + [ + 0.1111111111111111, + "#482878" + ], + [ + 0.2222222222222222, + "#3e4989" + ], + [ + 0.3333333333333333, + "#31688e" + ], + [ + 0.4444444444444444, + "#26828e" + ], + [ + 0.5555555555555556, + "#1f9e89" + ], + [ + 0.6666666666666666, + "#35b779" + ], + [ + 0.7777777777777778, + "#6ece58" + ], + [ + 0.8888888888888888, + "#b5de2b" + ], + [ + 1, + "#fde725" + ] + ], + "type": "histogram2d" + } + ], + "histogram2dcontour": [ + { + "colorbar": { + "outlinewidth": 1, + "tickcolor": "rgb(36,36,36)", + "ticks": "outside" + }, + "colorscale": [ + [ + 0, + "#440154" + ], + [ + 0.1111111111111111, + "#482878" + ], + [ + 0.2222222222222222, + "#3e4989" + ], + [ + 0.3333333333333333, + "#31688e" + ], + [ + 0.4444444444444444, + "#26828e" + ], + [ + 0.5555555555555556, + "#1f9e89" + ], + [ + 0.6666666666666666, + "#35b779" + ], + [ + 0.7777777777777778, + "#6ece58" + ], + [ + 0.8888888888888888, + "#b5de2b" + ], + [ + 1, + "#fde725" + ] + ], + "type": "histogram2dcontour" + } + ], + "mesh3d": [ + { + "colorbar": { + "outlinewidth": 1, + "tickcolor": "rgb(36,36,36)", + "ticks": "outside" + }, + "type": "mesh3d" + } + ], + "parcoords": [ + { + "line": { + "colorbar": { + "outlinewidth": 1, + "tickcolor": "rgb(36,36,36)", + "ticks": "outside" + } + }, + "type": "parcoords" + } + ], + "pie": [ + { + "automargin": true, + "type": "pie" + } + ], + "scatter": [ + { + "fillpattern": { + "fillmode": "overlay", + "size": 10, + "solidity": 0.2 + }, + "type": "scatter" + } + ], + "scatter3d": [ + { + "line": { + "colorbar": { + "outlinewidth": 1, + "tickcolor": "rgb(36,36,36)", + "ticks": "outside" + } + }, + "marker": { + "colorbar": { + "outlinewidth": 1, + "tickcolor": "rgb(36,36,36)", + "ticks": "outside" + } + }, + "type": "scatter3d" + } + ], + "scattercarpet": [ + { + "marker": { + "colorbar": { + "outlinewidth": 1, + "tickcolor": "rgb(36,36,36)", + "ticks": "outside" + } + }, + "type": "scattercarpet" + } + ], + "scattergeo": [ + { + "marker": { + "colorbar": { + "outlinewidth": 1, + "tickcolor": "rgb(36,36,36)", + "ticks": "outside" + } + }, + "type": "scattergeo" + } + ], + "scattergl": [ + { + "marker": { + "colorbar": { + "outlinewidth": 1, + "tickcolor": "rgb(36,36,36)", + "ticks": "outside" + } + }, + "type": "scattergl" + } + ], + "scattermapbox": [ + { + "marker": { + "colorbar": { + "outlinewidth": 1, + "tickcolor": "rgb(36,36,36)", + "ticks": "outside" + } + }, + "type": "scattermapbox" + } + ], + "scatterpolar": [ + { + "marker": { + "colorbar": { + "outlinewidth": 1, + "tickcolor": "rgb(36,36,36)", + "ticks": "outside" + } + }, + "type": "scatterpolar" + } + ], + "scatterpolargl": [ + { + "marker": { + "colorbar": { + "outlinewidth": 1, + "tickcolor": "rgb(36,36,36)", + "ticks": "outside" + } + }, + "type": "scatterpolargl" + } + ], + "scatterternary": [ + { + "marker": { + "colorbar": { + "outlinewidth": 1, + "tickcolor": "rgb(36,36,36)", + "ticks": "outside" + } + }, + "type": "scatterternary" + } + ], + "surface": [ + { + "colorbar": { + "outlinewidth": 1, + "tickcolor": "rgb(36,36,36)", + "ticks": "outside" + }, + "colorscale": [ + [ + 0, + "#440154" + ], + [ + 0.1111111111111111, + "#482878" + ], + [ + 0.2222222222222222, + "#3e4989" + ], + [ + 0.3333333333333333, + "#31688e" + ], + [ + 0.4444444444444444, + "#26828e" + ], + [ + 0.5555555555555556, + "#1f9e89" + ], + [ + 0.6666666666666666, + "#35b779" + ], + [ + 0.7777777777777778, + "#6ece58" + ], + [ + 0.8888888888888888, + "#b5de2b" + ], + [ + 1, + "#fde725" + ] + ], + "type": "surface" + } + ], + "table": [ + { + "cells": { + "fill": { + "color": "rgb(237,237,237)" + }, + "line": { + "color": "white" + } + }, + "header": { + "fill": { + "color": "rgb(217,217,217)" + }, + "line": { + "color": "white" + } + }, + "type": "table" + } + ] + }, + "layout": { + "annotationdefaults": { + "arrowhead": 0, + "arrowwidth": 1 + }, + "autotypenumbers": "strict", + "coloraxis": { + "colorbar": { + "outlinewidth": 1, + "tickcolor": "rgb(36,36,36)", + "ticks": "outside" + } + }, + "colorscale": { + "diverging": [ + [ + 0, + "rgb(103,0,31)" + ], + [ + 0.1, + "rgb(178,24,43)" + ], + [ + 0.2, + "rgb(214,96,77)" + ], + [ + 0.3, + "rgb(244,165,130)" + ], + [ + 0.4, + "rgb(253,219,199)" + ], + [ + 0.5, + "rgb(247,247,247)" + ], + [ + 0.6, + "rgb(209,229,240)" + ], + [ + 0.7, + "rgb(146,197,222)" + ], + [ + 0.8, + "rgb(67,147,195)" + ], + [ + 0.9, + "rgb(33,102,172)" + ], + [ + 1, + "rgb(5,48,97)" + ] + ], + "sequential": [ + [ + 0, + "#440154" + ], + [ + 0.1111111111111111, + "#482878" + ], + [ + 0.2222222222222222, + "#3e4989" + ], + [ + 0.3333333333333333, + "#31688e" + ], + [ + 0.4444444444444444, + "#26828e" + ], + [ + 0.5555555555555556, + "#1f9e89" + ], + [ + 0.6666666666666666, + "#35b779" + ], + [ + 0.7777777777777778, + "#6ece58" + ], + [ + 0.8888888888888888, + "#b5de2b" + ], + [ + 1, + "#fde725" + ] + ], + "sequentialminus": [ + [ + 0, + "#440154" + ], + [ + 0.1111111111111111, + "#482878" + ], + [ + 0.2222222222222222, + "#3e4989" + ], + [ + 0.3333333333333333, + "#31688e" + ], + [ + 0.4444444444444444, + "#26828e" + ], + [ + 0.5555555555555556, + "#1f9e89" + ], + [ + 0.6666666666666666, + "#35b779" + ], + [ + 0.7777777777777778, + "#6ece58" + ], + [ + 0.8888888888888888, + "#b5de2b" + ], + [ + 1, + "#fde725" + ] + ] + }, + "colorway": [ + "#1F77B4", + "#FF7F0E", + "#2CA02C", + "#D62728", + "#9467BD", + "#8C564B", + "#E377C2", + "#7F7F7F", + "#BCBD22", + "#17BECF" + ], + "font": { + "color": "rgb(36,36,36)" + }, + "geo": { + "bgcolor": "white", + "lakecolor": "white", + "landcolor": "white", + "showlakes": true, + "showland": true, + "subunitcolor": "white" + }, + "hoverlabel": { + "align": "left" + }, + "hovermode": "closest", + "mapbox": { + "style": "light" + }, + "paper_bgcolor": "white", + "plot_bgcolor": "white", + "polar": { + "angularaxis": { + "gridcolor": "rgb(232,232,232)", + "linecolor": "rgb(36,36,36)", + "showgrid": false, + "showline": true, + "ticks": "outside" + }, + "bgcolor": "white", + "radialaxis": { + "gridcolor": "rgb(232,232,232)", + "linecolor": "rgb(36,36,36)", + "showgrid": false, + "showline": true, + "ticks": "outside" + } + }, + "scene": { + "xaxis": { + "backgroundcolor": "white", + "gridcolor": "rgb(232,232,232)", + "gridwidth": 2, + "linecolor": "rgb(36,36,36)", + "showbackground": true, + "showgrid": false, + "showline": true, + "ticks": "outside", + "zeroline": false, + "zerolinecolor": "rgb(36,36,36)" + }, + "yaxis": { + "backgroundcolor": "white", + "gridcolor": "rgb(232,232,232)", + "gridwidth": 2, + "linecolor": "rgb(36,36,36)", + "showbackground": true, + "showgrid": false, + "showline": true, + "ticks": "outside", + "zeroline": false, + "zerolinecolor": "rgb(36,36,36)" + }, + "zaxis": { + "backgroundcolor": "white", + "gridcolor": "rgb(232,232,232)", + "gridwidth": 2, + "linecolor": "rgb(36,36,36)", + "showbackground": true, + "showgrid": false, + "showline": true, + "ticks": "outside", + "zeroline": false, + "zerolinecolor": "rgb(36,36,36)" + } + }, + "shapedefaults": { + "fillcolor": "black", + "line": { + "width": 0 + }, + "opacity": 0.3 + }, + "ternary": { + "aaxis": { + "gridcolor": "rgb(232,232,232)", + "linecolor": "rgb(36,36,36)", + "showgrid": false, + "showline": true, + "ticks": "outside" + }, + "baxis": { + "gridcolor": "rgb(232,232,232)", + "linecolor": "rgb(36,36,36)", + "showgrid": false, + "showline": true, + "ticks": "outside" + }, + "bgcolor": "white", + "caxis": { + "gridcolor": "rgb(232,232,232)", + "linecolor": "rgb(36,36,36)", + "showgrid": false, + "showline": true, + "ticks": "outside" + } + }, + "title": { + "x": 0.05 + }, + "xaxis": { + "automargin": true, + "gridcolor": "rgb(232,232,232)", + "linecolor": "rgb(36,36,36)", + "showgrid": false, + "showline": true, + "ticks": "outside", + "title": { + "standoff": 15 + }, + "zeroline": false, + "zerolinecolor": "rgb(36,36,36)" + }, + "yaxis": { + "automargin": true, + "gridcolor": "rgb(232,232,232)", + "linecolor": "rgb(36,36,36)", + "showgrid": false, + "showline": true, + "ticks": "outside", + "title": { + "standoff": 15 + }, + "zeroline": false, + "zerolinecolor": "rgb(36,36,36)" + } + } + }, + "xaxis": { + "anchor": "y", + "domain": [ + 0, + 1 + ], + "title": { + "text": "x" + } + }, + "yaxis": { + "anchor": "x", + "domain": [ + 0, + 1 + ], + "title": { + "text": "y" + } + } + } + }, + "text/html": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "import umap as umap\n", + "import plotly.express\n", + "\n", + "# Compute UMAP representation\n", + "reducer = umap.UMAP(random_state=1)\n", + "umap_coords = reducer.fit_transform(embeddings)\n", + "\n", + "plotly.express.scatter(x=umap_coords[:,0],\n", + " y=umap_coords[:,1],\n", + " template=\"simple_white\",\n", + " hover_name=cell_metadata.iloc[np.unique(test_pairs)]['hpa_crop_id'],\n", + " color=cell_metadata.iloc[np.unique(test_pairs)]['hpa_locations'])" + ] + } + ], + "metadata": { + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/subcellular.rst b/docs/subcellular.rst new file mode 100644 index 0000000..2039a1a --- /dev/null +++ b/docs/subcellular.rst @@ -0,0 +1,10 @@ +Subcellular Protein Localization +=================================================== +.. autoclass:: cajal.subcellular.CellAligner_Cell +.. autofunction:: cajal.subcellular.process_image +.. autofunction:: cajal.subcellular.gw_pairwise_parallel +.. autofunction:: cajal.subcellular.map_cell_to_cell +.. autofunction:: cajal.subcellular.map_to_anchor_cell +.. autofunction:: cajal.subcellular.gw_mapped_ot_pairwise_parallel +.. autofunction:: cajal.subcellular.find_centroid +.. autofunction:: cajal.subcellular.plot_cell_image \ No newline at end of file diff --git a/docs/subcellular_dl.rst b/docs/subcellular_dl.rst new file mode 100644 index 0000000..9d24a2a --- /dev/null +++ b/docs/subcellular_dl.rst @@ -0,0 +1,14 @@ +Subcellular Protein Localization Deep Learning +=================================================== +.. autofunction:: cajal.subcellular_dl.make_NN_training_data +.. autoclass:: cajal.subcellular_dl.dCellAlignerNetwork +.. autoclass:: cajal.subcellular_dl.PairedDataset +.. autoclass:: cajal.subcellular_dl.RandomHorizontalRescale +.. autofunction:: cajal.subcellular_dl.generate_dataset_split_pairs +.. autofunction:: cajal.subcellular_dl.pretrain_model +.. autofunction:: cajal.subcellular_dl.train_dCellAligner +.. autofunction:: cajal.subcellular_dl.load_dCellAligner_model +.. autofunction:: cajal.subcellular_dl.extract_embeddings +.. autofunction:: cajal.subcellular_dl.predict_distances +.. autofunction:: cajal.subcellular_dl.plot_distance_predictions +.. autofunction:: cajal.subcellular_dl.plot_reconstruction_comparison \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 9780c69..e308ab4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,67 +1,70 @@ -asttokens==2.4.1 -comm==0.2.2 -contourpy==1.0.6 -cycler==0.11.0 -Cython==3.0.10 -debugpy==1.8.1 -decorator==5.1.1 -dill==0.3.8 -executing==2.0.1 -fonttools==4.43.0 -igraph==0.10.2 -imageio==2.33.0 -ipykernel==6.29.4 -ipython==8.18.1 -ipywidgets==8.0.2 -jedi==0.19.1 -joblib==1.2.0 -jupyter_client==8.6.2 -jupyter_core==5.7.2 -jupyterlab-widgets==3.0.3 -kiwisolver==1.4.4 -lazy_loader==0.4 -leidenalg==0.10.2 -llvmlite==0.43.0 -matplotlib==3.9.0 -matplotlib-inline==0.1.7 -mpltern==1.0.4 -multiprocess==0.70.16 -nest-asyncio==1.6.0 -networkx==2.8.8 -numpy==2.0.0 -packaging==24.1 -pandas==2.2.2 -parso==0.8.4 -pathos==0.3.2 -pexpect==4.9.0 -pillow==11.0 -platformdirs==4.2.2 -potpourri3d==1.1.0 -pox==0.3.4 -ppft==1.7.6.8 -prompt_toolkit==3.0.47 -psutil==6.0.0 -ptyprocess==0.7.0 -pure-eval==0.2.2 -Pygments==2.18.0 -pyparsing==3.1.2 -python-dateutil==2.9.0.post0 -python-louvain==0.16 -pytz==2024.1 -pyzmq==26.0.3 -scikit-dimension==0.3.4 -scikit-image==0.24.0 -scikit-learn==1.5.0 -scipy==1.14.1 -six==1.16.0 -stack-data==0.6.3 -texttable==1.6.4 -threadpoolctl==3.1.0 -tifffile==2022.10.10 -tornado==6.4.2 -tqdm==4.66.4 -traitlets==5.14.3 -trimesh==3.16.1 -tzdata==2024.1 -wcwidth==0.2.13 -widgetsnbextension==4.0.3 +asttokens==2.4.1 +comm==0.2.2 +contourpy==1.0.6 +cycler==0.11.0 +Cython==3.0.10 +debugpy==1.8.1 +decorator==5.1.1 +dill==0.3.8 +executing==2.0.1 +fonttools==4.43.0 +igraph==0.10.2 +imageio==2.33.0 +ipykernel==6.29.4 +ipython==8.18.1 +ipywidgets==8.0.2 +jedi==0.19.1 +joblib==1.2.0 +jupyter_client==8.6.2 +jupyter_core==5.7.2 +jupyterlab-widgets==3.0.3 +kiwisolver==1.4.4 +lazy_loader==0.4 +leidenalg==0.10.2 +llvmlite==0.43.0 +matplotlib==3.9.0 +matplotlib-inline==0.1.7 +mpltern==1.0.4 +multiprocess==0.70.16 +nest-asyncio==1.6.0 +networkx==2.8.8 +numpy==2.0.0 +packaging==24.1 +pandas==2.2.2 +parso==0.8.4 +pathos==0.3.2 +pexpect==4.9.0 +pillow==11.0 +platformdirs==4.2.2 +potpourri3d==1.1.0 +POT==0.9.5 +pox==0.3.4 +ppft==1.7.6.8 +prompt_toolkit==3.0.47 +psutil==6.0.0 +ptyprocess==0.7.0 +pure-eval==0.2.2 +Pygments==2.18.0 +pyparsing==3.1.2 +python-dateutil==2.9.0.post0 +python-louvain==0.16 +pytz==2024.1 +pyzmq==26.0.3 +scikit-dimension==0.3.4 +scikit-image==0.24.0 +scikit-learn==1.5.0 +scipy==1.14.1 +six==1.16.0 +stack-data==0.6.3 +texttable==1.6.4 +threadpoolctl==3.1.0 +tifffile==2022.10.10 +tornado==6.4.2 +torch==2.4.0 +torchvision==0.19.0 +tqdm==4.66.4 +traitlets==5.14.3 +trimesh==3.16.1 +tzdata==2024.1 +wcwidth==0.2.13 +widgetsnbextension==4.0.3 diff --git a/src/cajal/subcellular.py b/src/cajal/subcellular.py new file mode 100644 index 0000000..23632a5 --- /dev/null +++ b/src/cajal/subcellular.py @@ -0,0 +1,925 @@ +import numpy as np +import pandas as pd +import os +import skimage as ski +import matplotlib.pyplot as plt +from tqdm import tqdm +import pickle +import copy +from scipy.spatial.distance import cdist, pdist, squareform +from multiprocessing import Pool, cpu_count +import itertools as it +import ot +import warnings +import copy + +from .sample_seg import cell_boundaries +from .gw_cython import gw_cython_core + + +def make_cell_image(cellaligner_cell, channels, make_square=False): + """Create a multi-channel image array from a CellAligner_Cell object. + + This function generates a multi-channel image array where the first + channel contains the cell segmentation mask and subsequent channels + contain normalized intensity values for the specified channels. + + :param cellaligner_cell: Either a CellAligner_Cell object or a path to a pickled CellAligner_Cell object. + :type cellaligner_cell: CellAligner_Cell or str + :param channels: List of channel names to include in the image. Each channel should + correspond to a key in the CellAligner_Cell's intensities dictionary or + be 'nucleus' for nuclear segmentation. + :type channels: list[str] + :param make_square: If True, pad the image to a square shape before returning. + :type make_square: bool + :return: 3D array of shape (height, width, len(channels)+1) where the first + channel (index 0) contains the segmentation mask and subsequent + channels contain normalized intensity values (0-1 range). + :rtype: numpy.ndarray + """ + # Load CellAligner_Cell object if path specified + if isinstance(cellaligner_cell, str): + with open(cellaligner_cell, 'rb') as file: + cellaligner_cell = pickle.load(file) + coords = cellaligner_cell.coords + coords[:,0] = coords[:,0] - coords[:,0].min() + coords[:,1] = coords[:,1] - coords[:,1].min() + # make new (n_channel + 1) x cell_width x cell_len image array + cell_image = np.zeros((coords[:,0].max()+1, coords[:,1].max()+1, len(channels)+1)) + for coord_i in range(len(coords)): + i,j = coords[coord_i] + cell_image[i,j,0] = 1 # store segmentation mask + for channel_i in range(len(channels)): # store channel pixel intensities + channel = channels[channel_i] + if channel == 'nucleus': + cell_image[i,j,channel_i+1] = cellaligner_cell.nucleus[coord_i] + else: + cell_image[i,j,channel_i+1] = cellaligner_cell.intensities[channel][coord_i] + for channel_i in range(len(channels)): + cell_image[:,:,channel_i+1] -= cell_image[:,:,channel_i+1].min() + cell_image[:,:,channel_i+1] /= cell_image[:,:,channel_i+1].max() + if make_square: + max_dim = max(cell_image.shape[0], cell_image.shape[1]) + cell_image = to_shape(cell_image, (max_dim, max_dim, cell_image.shape[2])) + return(cell_image) + + +def to_shape(a, shape): + """Pad a 3D array to match a target shape. + + Centers the input array within the target shape by adding symmetric + padding with zeros. This is useful for standardizing array dimensions + for visualization or analysis. + + :param a: numpy.ndarray + Input 3D array to be padded. + :param shape: tuple of int + Target shape as (z, y, x) dimensions. + :return: numpy.ndarray + Padded array with the specified target shape, centered with + zero-padding. + :rtype: numpy.ndarray + """ + z_, y_, x_ = shape + z, y, x = a.shape + z_pad = (z_-z) + y_pad = (y_-y) + x_pad = (x_-x) + return np.pad(a,((z_pad//2, z_pad//2 + z_pad%2), + (y_pad//2, y_pad//2 + y_pad%2), + (x_pad//2, x_pad//2 + x_pad%2)), + mode = 'constant') + + +def make_cell_image_for_plot(image, mask_alpha=0.2): + """Prepare a cell image for visualization by creating an RGB composite. + + Converts a multi-channel cell image into an RGB format suitable for + plotting. Adds a transparent cell mask overlay and reorders channels + to blue-red-green color scheme. + + :param image: Multi-channel cell image with shape (height, width, channels). + Channel 0 should contain the cell segmentation mask. + :type image: numpy.ndarray + :param mask_alpha: Transparency level for the cell mask overlay, by default 0.2. + :type mask_alpha: float + :return: RGB image array with shape (height, width, 3) suitable for + visualization with channel ordering as blue, red, green. + :rtype: numpy.ndarray + """ + im = np.zeros((image.shape[0], image.shape[1], 3)) + mask = image[:,:,0].copy() + for channel_i in range(1, image.shape[2]): + # add transparent cell mask to channel + im[:,:,channel_i-1] = image[:,:,channel_i] + (mask * mask_alpha) + # rescale channel + im[:,:,channel_i-1] = im[:,:,channel_i-1] / im[:,:,channel_i-1].max() + # add mask to empty channels if any + for channel_i in range(image.shape[2], 4): + im[:,:,channel_i-1] = im[:,:,channel_i-1] + (mask * mask_alpha) + # reorder channel color ordering to blue, red, green + im = im[:,:,[1,2,0]] + return(im) + + +def plot_cell_image(cellaligner_cell, channels, make_square=True, ax=None, mask_alpha=0.2): + """Plot a cell image with the specified channels as an RGB composite. + + Creates a visualization of cell image data by combining multiple channels + into an RGB representation with an optional transparent mask overlay. + + :param image: 3D numpy array of shape (H, W, C) representing the multi-channel image. + :type image: numpy.ndarray + :param channels: List of channel names corresponding to the last dimension of the image. + :type channels: list[str] + :param cell_mask_image: 2D numpy array of shape (H, W) with integer labels for each cell (0 for background). + :type cell_mask_image: numpy.ndarray + :param nucleus_mask_image: 2D numpy array of shape (H, W) with integer labels for nuclei (0 for background). Default is None. + :type nucleus_mask_image: numpy.ndarray or None + :param ds_factor: Downsampling factor. If provided, downsample by this factor. Default is None. + :type ds_factor: int or None + :param ds_target_size: Target number of pixels per cell after downsampling. If provided, downsample to achieve this pixel count. Default is None. + :type ds_target_size: int or None + :param filter_border_cells: If True, exclude cells touching the image border. Default is True. + :type filter_border_cells: bool + :param n_boundary_points: Number of points to sample from the cell boundary. If None, boundary sampling is skipped. Default is 100. + :type n_boundary_points: int or None + :param save_path: Directory path to save the processed cell objects as pickle files. If None, objects are not saved. Default is None. + :type save_path: str or None + :param return_objects: If True, return the list of CellAligner_Cell objects. Default is True. + :type return_objects: bool + + :return: + If return_objects is True, returns a list of CellAligner_Cell objects; otherwise returns None. + :rtype: list[CellAligner_Cell] or None + """ + if len(channels) > 3: + raise ValueError("Only up to 3 channels can be plotted.") + image = make_cell_image(cellaligner_cell, channels, make_square=make_square) + image = make_cell_image_for_plot(image, mask_alpha=mask_alpha) + if ax: + return(ax.imshow(image)) + else: + plt.imshow(image) + + +def rescale_mask_to_pixel_count(mask, target_pixels, max_iter=20, tolerance=0.01): + """Rescale a binary mask to achieve a target number of non-zero pixels. + + Iteratively adjusts the scale factor to resize a binary mask until the + number of non-zero pixels is close to the target value. Uses skimage's + resize function with nearest neighbor interpolation to maintain binary + nature of the mask. + + :param mask : numpy.ndarray + 2D binary numpy array containing 0s and 1s. + :param target_pixels : int + Desired number of non-zero pixels in the rescaled mask. + :param max_iter : int, optional + Maximum number of scaling iterations, by default 20. + :param tolerance : float, optional + Acceptable relative error as a fraction, by default 0.01 (1%). + :return: numpy.ndarray + Rescaled binary mask with pixel count close to target, maintaining + binary values (0s and 1s). + :rtype: numpy.ndarray + """ + current_pixels = np.count_nonzero(mask) + + # If mask is already close enough, return it + if abs(current_pixels - target_pixels) / target_pixels < tolerance: + return mask + + # Initial scale factor estimate (area scales with square of linear dimensions) + scale_factor = np.sqrt(target_pixels / current_pixels) + + for _ in range(max_iter): + # Calculate new dimensions (maintaining aspect ratio) + h, w = mask.shape + new_h = max(1, int(h * scale_factor)) + new_w = max(1, int(w * scale_factor)) + + # Resize the mask using skimage.transform.resize + resized_mask = ski.transform.resize(mask.astype(float), + (new_h, new_w), + order=0, # nearest neighbor interpolation + preserve_range=True, + anti_aliasing=False) + + # Binarize the result (threshold at 0.5 to maintain binary nature) + resized_mask = (resized_mask > 0.5).astype(np.uint8) + + # Count current non-zero pixels + current_pixels = np.count_nonzero(resized_mask) + + # Check if we're within tolerance + if abs(current_pixels - target_pixels) / target_pixels < tolerance: + return resized_mask + + # Update scale factor based on current error + scale_factor *= np.sqrt(target_pixels / current_pixels) + + # Return the best result if max iterations reached + return resized_mask + + +def compute_geodesic_dmat(mask_coords): + """Compute geodesic distance matrix for given coordinates within a binary mask. + + Calculates the shortest path distances between all pairs of coordinates + that lie within a connected binary mask region. Uses the MCP_Geometric + algorithm from scikit-image to compute geodesic distances. + + :param mask_coords : numpy.ndarray + 2D array of shape (N, 2) containing (x, y) coordinates of pixels + within the binary mask. + :return: numpy.ndarray + Symmetric distance matrix of shape (N, N) where element (i, j) + contains the geodesic distance between coordinates i and j. + :rtype: numpy.ndarray + """ + mask_coords[:,0] = mask_coords[:,0] - mask_coords[:,0].min() + mask_coords[:,1] = mask_coords[:,1] - mask_coords[:,1].min() + cell_mask = np.zeros((mask_coords[:,0].max()+1, mask_coords[:,1].max()+1)) + cell_mask[mask_coords[:,0], mask_coords[:,1]] = 1 + # Initialize MCP_Geometric with the mask (cost=1 for foreground, inf for background) + cost_array = np.where(cell_mask > 0, 1, np.inf) + mcp = ski.graph.MCP_Geometric(cost_array) + # Compute geodesic distances from each pixel to all others + N = len(mask_coords) + geodesic_dmat = np.zeros((N, N)) + for i, start in enumerate(mask_coords): + costs, traceback = mcp.find_costs([tuple(start)]) + geodesic_dmat[i] = costs[mask_coords[:,0], mask_coords[:,1]] + return(geodesic_dmat) + + + +class CellAligner_Cell: + """A cell representation for CellAligner analysis. + + This class encapsulates cell morphology and intensity information needed + for Gromov-Wasserstein mapping and distance computations between cells. + + :param coords: Array of shape (N, 2) containing (x, y) coordinates for each pixel in the cell. + :type coords: numpy.ndarray + :param boundary_coords: Array of shape (M, 2) containing (x, y) coordinates sampled from the cell boundary. Default is None. + :type boundary_coords: numpy.ndarray or None + :param intensities: Dictionary mapping channel names (str) to intensity arrays (numpy.ndarray) + of length N, where N is the number of cell pixels. Default is None. + :type intensities: dict or None + :param nucleus: Array of length N indicating nuclear identity (0 or 1) for each cell pixel. Default is None. + :type nucleus: numpy.ndarray or None + :param metric: Distance metric for computing coordinate distance matrices. Options are 'euclidean', 'geodesic', or None. Default is 'euclidean'. + :type metric: str or None + """ + def __init__(self, coords, boundary_coords=None, intensities=None, nucleus=None, metric='euclidean'): + self.coords = coords + self.boundary_coords = boundary_coords + if metric is None: + self.coord_dmat = None + self.boundary_coord_dmat = None + elif metric == 'geodesic': + self.coord_dmat = compute_geodesic_dmat(self.coords) + self.boundary_coord_dmat = squareform(pdist(boundary_coords, metric='euclidean')) if boundary_coords is not None else None + # if boundary_coords is not None: + # warnings.warn("Geodesic distance matrix cannot be computed for cell boundary coordinates, ignoring.") + else: + self.coord_dmat = squareform(pdist(coords, metric=metric)) + self.boundary_coord_dmat = squareform(pdist(boundary_coords, metric=metric)) if boundary_coords is not None else None + self.intensities = intensities if intensities is not None else {} + self.nucleus = nucleus + self.size = len(coords) + + def copy(self): + """Return a deep copy of the CellAligner_Cell instance. + + Returns + ------- + CellAligner_Cell + A new CellAligner_Cell instance with copied data from the current object. + All arrays and dictionaries are deep-copied to avoid shared references. + """ + obj_copy = CellAligner_Cell( + coords=self.coords.copy(), + boundary_coords=self.boundary_coords.copy() if self.boundary_coords is not None else None, + intensities=copy.deepcopy(self.intensities), + nucleus=self.nucleus.copy() if self.nucleus is not None else None + ) + obj_copy.coord_dmat = self.coord_dmat.copy() if self.coord_dmat is not None else None + obj_copy.boundary_coord_dmat = self.boundary_coord_dmat.copy() if self.boundary_coord_dmat is not None else None + obj_copy.size = self.size + return obj_copy + + +# Backward compatibility for pickle files created before the class rename. +GW_OT_Cell = CellAligner_Cell + + +def process_image(image, channels, cell_mask_image, nucleus_mask_image=None, ds_factor=None, ds_target_size=None, + filter_border_cells=True, n_boundary_points=100, save_path=None, return_objects=True): + """Create CellAligner_Cell objects from segmented microscopy images. + + Processes a multi-channel microscopy image with cell and nuclear segmentation + masks to create a list of CellAligner_Cell objects suitable for CellAligner analysis. + + :param image: numpy.ndarray + 3D numpy array of shape (H, W, C) representing the multi-channel image. + :param channels: list of str + List of channel names corresponding to the last dimension of the image. + :param cell_mask_image: numpy.ndarray + 2D numpy array of shape (H, W) with integer labels for each cell + (0 for background). + :param nucleus_mask_image: numpy.ndarray, optional + 2D numpy array of shape (H, W) with integer labels for nuclei + (0 for background). Default is None. + :param ds_factor: int, optional + Downsampling factor. If provided, downsample by this factor. + Default is None. + :param ds_target_size: int, optional + Target number of pixels per cell after downsampling. If provided, + downsample to achieve this pixel count. Default is None. + :param filter_border_cells: bool, optional + If True, exclude cells touching the image border. Default is True. + :param n_boundary_points: int, optional + Number of points to sample from the cell boundary. If None, boundary + sampling is skipped. Default is 100. + :param save_path: str, optional + Directory path to save the processed cell objects as pickle files. + If None, objects are not saved. Default is None. + :param return_objects: bool, optional + If True, return the list of CellAligner_Cell objects. Default is True. + :return: If return_objects is True, returns a list of CellAligner_Cell objects; otherwise returns None. + :rtype: list[CellAligner_Cell] or None + """ + cell_inds = np.unique(cell_mask_image) + cell_inds = cell_inds[cell_inds > 0] # Remove background (0) + + cellaligner_cells = [] + for cell_ind in cell_inds: + cell_mask = (cell_mask_image == cell_ind).astype(np.uint8) + nuc_mask = (nucleus_mask_image == cell_ind).astype(np.uint8) if nucleus_mask_image is not None else None + # Filter out cells touching the border + if filter_border_cells: + if np.any(cell_mask[0, :]) or np.any(cell_mask[-1, :]) or np.any(cell_mask[:, 0]) or np.any(cell_mask[:, -1]): + continue + # Downsample image and masks if necessary + if ds_factor is not None: # downsample by a factor + image_ds = ski.transform.resize(image, (image.shape[0] // ds_factor, image.shape[1] // ds_factor, image.shape[2]), order=1, preserve_range=True) + cell_mask_ds = ski.transform.resize(cell_mask, (cell_mask.shape[0] // ds_factor, cell_mask.shape[1] // ds_factor), order=0, anti_aliasing=False, preserve_range=True).astype(np.uint8) + nuc_mask_ds = ski.transform.resize(nuc_mask, (nuc_mask.shape[0] // ds_factor, nuc_mask.shape[1] // ds_factor), order=0, anti_aliasing=False, preserve_range=True).astype(np.uint8) if nuc_mask is not None else None + elif ds_target_size is not None: # downsample to a target size + cell_mask_ds = rescale_mask_to_pixel_count(cell_mask, target_pixels=ds_target_size) + nuc_mask_ds = ski.transform.resize(nuc_mask, (cell_mask_ds.shape[0], cell_mask_ds.shape[1]), order=0, anti_aliasing=False, preserve_range=True).astype(np.uint8) if nuc_mask is not None else None + image_ds = ski.transform.resize(image, (cell_mask_ds.shape[0], cell_mask_ds.shape[1], image.shape[2]), order=1, preserve_range=True) + else: + image_ds = image + cell_mask_ds = cell_mask + nuc_mask_ds = nuc_mask + # Create a cell object + # Sample points from cell boundary (if specified) + cell_boundary_pts = None + if n_boundary_points is not None: + _, cell_boundary_pts = cell_boundaries(np.pad(cell_mask, 1), n_sample=n_boundary_points)[0] # pad to avoid border issues + cell_boundary_pts = cell_boundary_pts - 1 # remove padding + cellaligner_cell = CellAligner_Cell(coords=np.array(np.where(cell_mask_ds)).T, boundary_coords=cell_boundary_pts) + if nucleus_mask_image is not None: + cellaligner_cell.nucleus = nuc_mask_ds[np.where(cell_mask_ds)] + if cellaligner_cell.nucleus.sum() == 0: # filter cells without segmented nuclei + continue + for channel in channels: + cellaligner_cell.intensities[channel] = image_ds[np.where(cell_mask_ds)][:,channels.index(channel)] + # Normalize the channels (to sum to 1) + for channel in channels: + cellaligner_cell.intensities[channel] = cellaligner_cell.intensities[channel] / np.sum(cellaligner_cell.intensities[channel]) + if return_objects: + cellaligner_cells.append(cellaligner_cell) + if save_path is not None: + if not os.path.isdir(save_path): # Create directory if it doesn't exist + os.makedirs(save_path) + with open(os.path.join(save_path, 'cell_'+str(cell_ind).zfill(4)+'.pickle'), 'wb') as file: + pickle.dump(cellaligner_cell, file) + if return_objects: + return cellaligner_cells + else: + return None + + +def _init_gw_pool(cell_objects: list, points: str): + """Initialize global variables for parallel Gromov-Wasserstein computation. + + This function sets up shared state for multiprocessing workers computing + pairwise Gromov-Wasserstein distances. + + :param cell_objects: list + List of CellAligner_Cell objects or paths to pickled CellAligner_Cell objects. + :param points: str + Type of points to use for distance computation. Either 'boundary' + for boundary coordinates or 'full' for all cell coordinates. + """ + # list of CellAligner_Cell objects or list of paths to CellAligner_Cell objects + global _CELL_OBJECTS + _CELL_OBJECTS = cell_objects + # set of points to use for distance computation ('boundary' or 'full') + global _POINTS + _POINTS = points + + +def _gw_index(p: tuple[int, int]): + """Worker that computes the GW coupling and distance for a pair of cells. + + :param p: tuple (i, j) of cell indices + :type p: tuple[int, int] + :return: (i, j, coupling_mat, gw_dist) + :rtype: tuple[int, int, numpy.ndarray, float] + """ + i, j = p + # load CellAligner_Cell objects if path specified + if isinstance(_CELL_OBJECTS[i], str): + _CELL_OBJECTS[i] = pickle.load(open(_CELL_OBJECTS[i], 'rb')) + if isinstance(_CELL_OBJECTS[j], str): + _CELL_OBJECTS[j] = pickle.load(open(_CELL_OBJECTS[j], 'rb')) + if _POINTS == 'boundary': + A = _CELL_OBJECTS[i].boundary_coord_dmat + B = _CELL_OBJECTS[j].boundary_coord_dmat + elif _POINTS == 'full': + A = _CELL_OBJECTS[i].coord_dmat + B = _CELL_OBJECTS[j].coord_dmat + else: + raise ValueError("Invalid value for _POINTS. Must be 'boundary' or 'full'.") + n_A = A.shape[0] + n_B = B.shape[0] + a = np.repeat(1/n_A, n_A) + b = np.repeat(1/n_B, n_B) + a_dot_dist = A@a + b_dot_dist = B@b + a_cell_constant = ((A * A)@a)@a + b_cell_constant = ((B * B)@b)@b + + coupling_mat, gw_dist = gw_cython_core( + A, + a, + a_dot_dist, + a_cell_constant, + B, + b, + b_dot_dist, + b_cell_constant, + ) + + return (i, j, coupling_mat, gw_dist) + +def gw_pairwise_parallel(cell_objects, points='boundary', num_processes=4, chunksize=20, n_approx_anchors=None, initial_anchor=0): + """Compute pairwise Gromov-Wasserstein distances (optionally in parallel). + + Calculates the Gromov-Wasserstein distance matrix for a colloection of cells + using either exact computation or traiangle inequality approximation with anchors. + + :param cell_objects: list of CellAligner_Cell objects or file paths + :type cell_objects: list + :param points: 'boundary' or 'full' (default: 'boundary') + :type points: str + :param num_processes: number of parallel processes to use + :type num_processes: int + :param chunksize: chunk size for parallel imap + :type chunksize: int + :param n_approx_anchors: number of anchors for approximation (None = exact) + :type n_approx_anchors: int or None + :param initial_anchor: initial anchor index for approximation + :type initial_anchor: int + :return: symmetric GW distance matrix of shape (N, N) + :rtype: numpy.ndarray + """ + N = len(cell_objects) + # Compute all pairwise GW distances + if n_approx_anchors is None: + index_pairs = it.combinations(iter(range(N)), 2) + total_num_pairs = int((N * (N - 1)) / 2) + with Pool( + initializer=_init_gw_pool, initargs=(cell_objects,points,), processes=num_processes + ) as pool: + res = pool.imap_unordered(_gw_index, index_pairs, chunksize=chunksize) + gw_dmat = np.zeros((N,N)) + for i, j, coupling_mat, gw_dist in tqdm(res, total=total_num_pairs, position=0, leave=True): + gw_dmat[i,j] = gw_dist + gw_dmat[j,i] = gw_dist + # Approximate GW distances using triangle inequality + else: + anchor_ind = initial_anchor + all_anchor_gw_dists = np.zeros((n_approx_anchors,N)) + for i_anchor in range(n_approx_anchors): + anchor_gw_dists = np.zeros(N) + index_pairs = it.product(iter(range(N)), [anchor_ind]) + total_num_pairs = N + with Pool( + initializer=_init_gw_pool, initargs=(cell_objects,points,), processes=num_processes + ) as pool: + res = pool.imap_unordered(_gw_index, index_pairs, chunksize=chunksize) + for i, j, coupling_mat, gw_dist in tqdm(res, total=total_num_pairs): + anchor_gw_dists[i] = gw_dist + all_anchor_gw_dists[i_anchor,:] = anchor_gw_dists + anchor_ind = np.argmax(all_anchor_gw_dists[:i_anchor+1,:].min(axis=0)) # next anchor + gw_dmat = np.zeros((N,N)) + for i,j in it.combinations(range(N), 2): + d = min(all_anchor_gw_dists[:,i] + all_anchor_gw_dists[:,j]) + gw_dmat[i,j] = d + gw_dmat[j,i] = d + return gw_dmat + + +def find_centroid(distance_matrix): + """Return the index of the centroid point (minimizes sum of distances). + + :param distance_matrix: square symmetric pairwise distance matrix + :type distance_matrix: numpy.ndarray + :return: index of centroid point + :rtype: int + """ + sum_distances = np.sum(distance_matrix, axis=1) + centroid_index = np.argmin(sum_distances) + return centroid_index + + +def _init_fgw_map_pool(cell_objects: list, channels: list, compartment_specific: bool, method, + fused_channel: str, fused_cost: float, fused_param: float, unbalanced_param: float, + nuclear_fraction: float = 0.2): + """Initialize global state for parallel fused GW mapping workers. + + :param cell_objects: list of CellAligner_Cell objects or paths + :type cell_objects: list + :param channels: channel names to compute OT for + :type channels: list[str] + :param compartment_specific: whether to perform compartment-specific mapping + :type compartment_specific: bool + :param method: mapping method ('fused' or 'fused_unbalanced') + :type method: str + :param fused_channel: channel name used for fused mapping + :type fused_channel: str + :param fused_cost: fused channel cost multiplier + :type fused_cost: float + :param fused_param: alpha parameter for fused GW + :type fused_param: float + :param unbalanced_param: regularization for unbalanced fused GW + :type unbalanced_param: float + :param nuclear_fraction: fraction considered nuclear during mapping + :type nuclear_fraction: float + """ + global _CELL_OBJECTS # + _CELL_OBJECTS = cell_objects # list of CellAligner_Cell objects or list of paths to CellAligner_Cell objects + global _CHANNELS + _CHANNELS = channels # which channels to compute protein OT for + global _COMPARTMENT_SPECIFIC + _COMPARTMENT_SPECIFIC = compartment_specific # whether to do compartment-specific mapping (nuclear/cytoplasm) + global _METHOD + _METHOD = method # method for morphology mapping: 'fused' or 'fused_unbalanced' + global _FUSED_CHANNEL + _FUSED_CHANNEL = fused_channel # channel to use for fused GW morphology mapping + global _FUSED_COST + _FUSED_COST = fused_cost # cost for fused GW morphology mapping + global _FUSED_PARAM + _FUSED_PARAM = fused_param # parameter for fused/unbalanced GW morphology mapping + global _UNBALANCED_PARAM + _UNBALANCED_PARAM = unbalanced_param # parameter for fused unbalanced GW mapping + global _NUC_FRAC + _NUC_FRAC = nuclear_fraction # fraction of cell considered as nucleus for compartment-specific + + +def _fgw_map_index(p: tuple[int, int]): + """Worker that computes fused GW mapping and maps protein distributions. + + :param p: tuple (i, j) of source and target cell indices + :type p: tuple[int, int] + :return: (i, j, gw_dist, mapped_distbs) + :rtype: tuple[int, int, float, numpy.ndarray] + """ + i, j = p + # load CellAligner_Cell objects if path specified + if isinstance(_CELL_OBJECTS[i], str): + _CELL_OBJECTS[i] = pickle.load(open(_CELL_OBJECTS[i], 'rb')) + if isinstance(_CELL_OBJECTS[j], str): + _CELL_OBJECTS[j] = pickle.load(open(_CELL_OBJECTS[j], 'rb')) + A = _CELL_OBJECTS[i].coord_dmat + B = _CELL_OBJECTS[j].coord_dmat + n_A = A.shape[0] + n_B = B.shape[0] + + # dictionary to store fGW morphology and OT protein distances + mapped_distbs = np.zeros((len(_CHANNELS),n_B)) + + if _COMPARTMENT_SPECIFIC: + # rescaling probabilities to allow fused GW to map nucleus to nucleus, cytoplasm to cytoplasm + n_pixel_nuc_i = _CELL_OBJECTS[i].nucleus.sum() + n_pixel_cyto_i = n_A - n_pixel_nuc_i + n_pixel_nuc_j = _CELL_OBJECTS[j].nucleus.sum() + n_pixel_cyto_j = n_B - n_pixel_nuc_j + + # rescale uniform distribution in cell i to have same nuclear/cytoplasm ration as cell j + a = np.zeros(n_A) + a[_CELL_OBJECTS[i].nucleus==1] = _NUC_FRAC / n_pixel_nuc_i + a[_CELL_OBJECTS[i].nucleus==0] = (1 - _NUC_FRAC) / n_pixel_cyto_i + b = np.zeros(n_B) + b[_CELL_OBJECTS[j].nucleus==1] = _NUC_FRAC / n_pixel_nuc_j + b[_CELL_OBJECTS[j].nucleus==0] = (1 - _NUC_FRAC) / n_pixel_cyto_j + else: + a = np.repeat(1/n_A, n_A) + b = np.repeat(1/n_B, n_B) + + # fGW morphology mapping + alpha = _FUSED_PARAM + cost = _FUSED_COST + rho = _UNBALANCED_PARAM + if i == j: + gw_dist = 0 + coupling_mat = np.eye(n_A) + else: + if _FUSED_CHANNEL == 'nucleus': + cost_matrix = cdist(_CELL_OBJECTS[i].nucleus[:,np.newaxis], _CELL_OBJECTS[j].nucleus[:,np.newaxis],) * cost + else: + cost_matrix = cdist(_CELL_OBJECTS[i].intensities[_FUSED_CHANNEL][:,np.newaxis], _CELL_OBJECTS[j].intensities[_FUSED_CHANNEL][:,np.newaxis],) * cost + if _METHOD == 'fused': + coupling_mat, log = ot.gromov.fused_gromov_wasserstein(M=cost_matrix, C1=A, C2=B, p=a, q=b, alpha=alpha, log=True) + gw_dist = log['fgw_dist'] + elif _METHOD == 'fused_unbalanced': + coupling_mat, coupling_mat_2, log = ot.gromov.fused_unbalanced_gromov_wasserstein(M=cost_matrix, Cx=A, Cy=B, wx=a, wy=b, alpha=alpha, + reg_marginals=rho, max_iter=20, log=True) + gw_dist = log['fugw_cost'] + else: + raise ValueError("Invalid value for _METHOD. Must be 'fused' or 'fused_unbalanced'.") + + if _COMPARTMENT_SPECIFIC: + # find nuclear pixels after mapping + mapped_nucleus = _CELL_OBJECTS[i].nucleus.dot(coupling_mat * n_A) + mapped_nucleus_thresh = ( np.quantile(mapped_nucleus, 0.9) + np.quantile(mapped_nucleus, 0.1) ) / 2 + mapped_is_nucleus = mapped_nucleus > mapped_nucleus_thresh + n_pixel_nuc_mapped = mapped_is_nucleus.sum() + if _METHOD == 'fused_unbalanced': + mapped_cyto = np.repeat(1, n_A).dot(coupling_mat * n_A) + mapped_cyto[mapped_is_nucleus] = 0 + mapped_cyto_thresh = ( np.quantile(mapped_cyto, 0.7) + np.quantile(mapped_cyto, 0.1) ) / 2 + mapped_is_cyto = mapped_cyto > mapped_cyto_thresh + n_pixel_cyto_mapped = mapped_is_cyto.sum() + mapping_cyto = np.repeat(1, n_B).dot(coupling_mat.T * n_B) + mapping_cyto[_CELL_OBJECTS[i].nucleus==1] = 0 + mapping_cyto_thresh = ( np.quantile(mapping_cyto, 0.7) + np.quantile(mapping_cyto, 0.1) ) / 2 + mapping_is_cyto = mapping_cyto > mapping_cyto_thresh + n_pixel_cyto_mapping = mapped_is_cyto.sum() + else: + n_pixel_cyto_mapped = n_B - n_pixel_nuc_mapped + + # mapping cell A's distribution on cell B + for k in range(len(_CHANNELS)): + channel = _CHANNELS[k] + + if channel == 'nucleus': + a = _CELL_OBJECTS[i].nucleus.dot(coupling_mat * n_A) + else: + a = _CELL_OBJECTS[i].intensities[channel].dot(coupling_mat * n_A) + + if _COMPARTMENT_SPECIFIC: + # rescale based on protein distribution in nucleus/cytoplasm + if _METHOD == 'fused_unbalanced': + if a[mapped_is_nucleus].sum() != 0: + a[mapped_is_nucleus] = a[mapped_is_nucleus] / a[mapped_is_nucleus].sum() * a[_CELL_OBJECTS[j].nucleus==1].sum() + if a[~mapped_is_nucleus].sum() != 0: + a[~mapped_is_nucleus] = a[~mapped_is_nucleus] / n_pixel_cyto_mapped * n_pixel_cyto_mapping + else: + if a[mapped_is_nucleus].sum() != 0: + a[mapped_is_nucleus] = a[mapped_is_nucleus] / a[mapped_is_nucleus].sum() * _CELL_OBJECTS[i].intensities[channel][_CELL_OBJECTS[i].nucleus==1].sum() + if a[~mapped_is_nucleus].sum() != 0: + a[~mapped_is_nucleus] = a[~mapped_is_nucleus] / a[~mapped_is_nucleus].sum() * _CELL_OBJECTS[i].intensities[channel][_CELL_OBJECTS[i].nucleus==0].sum() + # then rescale based on number nuclear/cytoplasm pixels + a[mapped_is_nucleus] *= n_pixel_nuc_j/n_B*n_A/n_pixel_nuc_i + a[~mapped_is_nucleus] *= n_pixel_cyto_j/n_B*n_A/n_pixel_cyto_i + + # final normalization + a = a / a.sum() + + mapped_distbs[k,:] = a + + return (i, j, gw_dist, mapped_distbs) + + +def map_cell_to_cell(cell_object_from, cell_object_to, channels, compartment_specific=True, method='fused', + fused_channel='protein', fused_cost=10, fused_param=0.1, unbalanced_param=70, nuclear_fraction=0.2): + """Map protein distributions from one cell onto another via Fused Gromov-Wasserstein. + + :param cell_object_from: source ``CellAligner_Cell`` + :type cell_object_from: CellAligner_Cell + :param cell_object_to: target ``CellAligner_Cell`` + :type cell_object_to: CellAligner_Cell + :param channels: list of channel names to map + :type channels: list[str] + :param compartment_specific: whether to use compartment-specific mapping + :type compartment_specific: bool + :param method: 'fused' or 'fused_unbalanced' + :type method: str + :param fused_channel: channel name to use for fused morphology cost + :type fused_channel: str + :param fused_cost: fused channel cost multiplier + :type fused_cost: float + :param fused_param: alpha parameter for fused GW + :type fused_param: float + :param unbalanced_param: regularization for unbalanced GW + :type unbalanced_param: float + :param nuclear_fraction: nuclear fraction for compartment scaling + :type nuclear_fraction: float + :return: mapped distributions with shape (len(channels), n_target_pixels) + :rtype: numpy.ndarray + """ + mapped_distbs = map_to_anchor_cell( + cell_objects=[cell_object_from, cell_object_to], + channels=channels, + target_cell_ind=1, + compartment_specific=compartment_specific, + method=method, + fused_channel=fused_channel, + fused_cost=fused_cost, + fused_param=fused_param, + unbalanced_param=unbalanced_param, + nuclear_fraction=nuclear_fraction, + # parallel=False + ) + return mapped_distbs[:,0,:] + + +def map_to_anchor_cell(cell_objects, channels, target_cell_ind, compartment_specific=True, method='fused', + fused_channel='protein', fused_cost=10, fused_param=0.1, unbalanced_param=70, nuclear_fraction=0.2, + # parallel=True, num_processes=4, chunksize=20 + ): + """Map protein distributions from all cells to a single target cell. + + :param cell_objects: list of CellAligner_Cell objects or file paths + :type cell_objects: list + :param channels: channel names to map + :type channels: list[str] + :param target_cell_ind: index of the target cell + :type target_cell_ind: int + :param compartment_specific: whether to use compartment-specific mapping + :type compartment_specific: bool + :param method: mapping method ('fused' or 'fused_unbalanced') + :type method: str + :param fused_channel: channel used for fused cost + :type fused_channel: str + :param fused_cost: cost multiplier for fused channel + :type fused_cost: float + :param fused_param: alpha parameter for fused GW + :type fused_param: float + :param unbalanced_param: regularization for unbalanced GW + :type unbalanced_param: float + :param nuclear_fraction: probablistic fraction considered nuclear for compartment-specific mapping (should roughly correspond to fraction of nuclear pixels) + :type nuclear_fraction: float + :param parallel: whether to run in parallel + """ + # :type parallel: bool + # :param num_processes: number of processes for parallel execution + # :type num_processes: int + # :param chunksize: chunk size for parallel map + # :type chunksize: int + # :return: array of mapped distributions with shape (len(channels), N, n_target_pixels) + # :rtype: numpy.ndarray + print('Mapping cells to target cell:') + N = len(cell_objects) + index_pairs = [(i, target_cell_ind) for i in range(N)] + total_num_pairs = N - 1 + # if parallel: + # # Parallelized + # with Pool( + # initializer=_init_fgw_map_pool, initargs=(cell_objects, channels, compartment_specific, method, + # fused_channel, fused_cost, fused_param, unbalanced_param, nuclear_fraction), + # processes=num_processes + # ) as pool: + # res = pool.imap_unordered(_fgw_map_index, index_pairs, chunksize=chunksize) + # target_cell_object = cell_objects[target_cell_ind] + # # load target CellAligner_Cell object if path specified + # if isinstance(target_cell_object, str): + # target_cell_object = pickle.load(open(target_cell_object, 'rb')) + # mapped_distbs = np.zeros((len(channels),N,target_cell_object.coord_dmat.shape[0])) + # for i, j, gw_dist, mapped_distb in tqdm(res, total=total_num_pairs, position=0, leave=True): + # mapped_distbs[:,i,:] = mapped_distb + # else: + # Non-parallelized + _init_fgw_map_pool(cell_objects, channels, compartment_specific, method, fused_channel, fused_cost, + fused_param, unbalanced_param, nuclear_fraction) + mapped_distbs = np.zeros((len(channels),N,cell_objects[target_cell_ind].coord_dmat.shape[0])) + for p in tqdm(index_pairs): + i, j, gw_dist, mapped_distb = _fgw_map_index(p) + mapped_distbs[:,i,:] = mapped_distb + return mapped_distbs + + +def _init_gw_mapped_ot_pool(cell_object: CellAligner_Cell, mapped_cell_dists: np.ndarray): + """Initialize global state for OT computation on mapped distributions. + + :param cell_object: target ``CellAligner_Cell`` containing coordinate distance matrix + :type cell_object: CellAligner_Cell + :param mapped_cell_dists: mapped distributions array (n_channels, N, n_target_pixels) + :type mapped_cell_dists: numpy.ndarray + """ + global _CELL_OBJECT + _CELL_OBJECT = cell_object # CellAligner_Cell object + global _MAPPED_CELL_DISTS + _MAPPED_CELL_DISTS = mapped_cell_dists # numpy array storing mapped cell protein distributions + + +def _gw_mapped_ot_index(p: tuple[int, int]): + """Worker that computes OT distances between two mapped distributions. + + :param p: tuple (i, j) of indices + :type p: tuple[int, int] + :return: (i, j, ot_dists) where ot_dists is an array per channel + :rtype: tuple[int, int, numpy.ndarray] + """ + global _CELL_OBJECT + i, j = p + if isinstance(_CELL_OBJECT, str): + _CELL_OBJECT = pickle.load(open(_CELL_OBJECT, 'rb')) + n_channels = _MAPPED_CELL_DISTS.shape[0] + ot_dists = np.zeros(n_channels) + + # protein OT + for channel_i in range(n_channels): + a = _MAPPED_CELL_DISTS[channel_i,i,:] + b = _MAPPED_CELL_DISTS[channel_i,j,:] + coupling_mat_prot, emd_dict = ot.emd(a, b, _CELL_OBJECT.coord_dmat, log=True) + gw_dist_prot = emd_dict['cost'] + ot_dists[channel_i] = gw_dist_prot + + return (i, j, ot_dists) + + +def gw_mapped_ot_pairwise_parallel(cell_object, mapped_cell_dists, num_processes=4, chunksize=20, index_pairs=None, n_approx_anchors=None, initial_anchor=0): + """Compute pairwise OT distances between mapped protein distributions. + + Calculates pairwise optimal transport distances for protein distribution after + mapping to a common cell morphology. + + :param cell_object: target ``CellAligner_Cell`` or path to pickled object + :type cell_object: CellAligner_Cell or str + :param mapped_cell_dists: array of mapped distributions (len(channels), N, n_target_pixels) + :type mapped_cell_dists: numpy.ndarray + :param num_processes: number of processes for parallel execution + :type num_processes: int + :param chunksize: chunk size for parallel imap + :type chunksize: int + :param index_pairs: optional iterable of (i, j) index pairs to compute + :type index_pairs: iterable[tuple[int, int]] or None + :param n_approx_anchors: number of anchors for triangle inequality approximation + :type n_approx_anchors: int or None + :param initial_anchor: initial anchor index + :type initial_anchor: int + :return: if ``index_pairs`` is None returns array (len(channels), N, N), else (len(channels), len(index_pairs)) + :rtype: numpy.ndarray + """ + print('Computing pairwise OT distances:') + N = mapped_cell_dists.shape[1] + # Compute all pairwise OT distances or specific specific pairs + if n_approx_anchors is None: + if index_pairs is None: + index_pairs = list(it.combinations(range(N), 2)) + total_num_pairs = int((N * (N - 1)) / 2) + output_full_matrix = True + else: + index_pairs = list(index_pairs) + total_num_pairs = len(index_pairs) + output_full_matrix = False + + with Pool( + initializer=_init_gw_mapped_ot_pool, initargs=(cell_object, mapped_cell_dists,), processes=num_processes + ) as pool: + res = pool.imap(_gw_mapped_ot_index, index_pairs, chunksize=chunksize) + if output_full_matrix: + ot_dmats = np.zeros((mapped_cell_dists.shape[0], N, N)) + for i, j, ot_dists in tqdm(res, total=total_num_pairs, position=0, leave=True): + ot_dmats[:, i, j] = ot_dists + ot_dmats[:, j, i] = ot_dists + return ot_dmats + else: + ot_dists_arr = np.zeros((mapped_cell_dists.shape[0], total_num_pairs)) + for idx, (i, j, ot_dists) in enumerate(tqdm(res, total=total_num_pairs, position=0, leave=True)): + ot_dists_arr[:, idx] = ot_dists + return ot_dists_arr + # Approximate OT distances using triangle inequality + else: + if index_pairs is not None: + raise ValueError("index_pairs cannot be specified when using triangle inequality approximation.") + anchor_ind = initial_anchor + all_anchor_ot_dists = np.zeros((mapped_cell_dists.shape[0], n_approx_anchors, N)) + for i_anchor in range(n_approx_anchors): + anchor_ot_dists = np.zeros((mapped_cell_dists.shape[0], N)) + index_pairs = it.product(iter(range(N)), [anchor_ind]) + total_num_pairs = N + with Pool( + initializer=_init_gw_mapped_ot_pool, initargs=(cell_object, mapped_cell_dists,), processes=num_processes + ) as pool: + res = pool.imap(_gw_mapped_ot_index, index_pairs, chunksize=chunksize) + for i, j, ot_dists in tqdm(res, total=total_num_pairs, position=0, leave=True): + anchor_ot_dists[:, i] = ot_dists + all_anchor_ot_dists[:,i_anchor,:] = anchor_ot_dists + anchor_ind = np.argmax(all_anchor_ot_dists.mean(axis=0)[:i_anchor+1,:].min(axis=0)) # next anchor + ot_dmats = np.zeros((mapped_cell_dists.shape[0], N, N)) + for channel_i in range(mapped_cell_dists.shape[0]): + for i,j in it.combinations(range(N), 2): + d = min(all_anchor_ot_dists[channel_i,i] + all_anchor_ot_dists[channel_i,j]) + ot_dmats[channel_i, i, j] = d + ot_dmats[channel_i, j, i] = d + return ot_dmats \ No newline at end of file diff --git a/src/cajal/subcellular_dl.py b/src/cajal/subcellular_dl.py new file mode 100644 index 0000000..ffb35ed --- /dev/null +++ b/src/cajal/subcellular_dl.py @@ -0,0 +1,1787 @@ +import os +import random +import itertools as it +import numpy as np +import skimage as ski +import torch +import torch.nn as nn +import torch.nn.functional as F +import torch.optim as optim +import torchvision.models as models +import torchvision.transforms.functional as TF +import torchvision.transforms.v2 as transforms +from torch.utils.data import Dataset, DataLoader +from tqdm import tqdm +import pickle +import json +from .subcellular import make_cell_image, to_shape + + +CELL_IMAGE_PROCESSING_FILENAME = 'cell_image_processing.json' + + +def _write_cell_image_processing_info(save_path, max_size, rescale, shape, center): + processing_info = { + 'version': 1, + 'max_size': None if max_size is None else int(max_size), + 'rescale': bool(rescale), + 'shape': [int(shape[0]), int(shape[1])], + 'center': center, + } + with open(os.path.join(save_path, CELL_IMAGE_PROCESSING_FILENAME), 'w', encoding='utf-8') as file: + json.dump(processing_info, file, indent=2) + + +def _load_cell_image_processing_info(processing_info_path): + if processing_info_path is None: + return None + + if os.path.isdir(processing_info_path): + processing_info_path = os.path.join(processing_info_path, CELL_IMAGE_PROCESSING_FILENAME) + + if not os.path.exists(processing_info_path): + return None + + with open(processing_info_path, 'r', encoding='utf-8') as file: + processing_info = json.load(file) + + if 'shape' in processing_info and processing_info['shape'] is not None: + processing_info['shape'] = tuple(processing_info['shape']) + + return processing_info + + +def _is_cellaligner_cell_like(value): + return hasattr(value, 'coords') and hasattr(value, 'intensities') and hasattr(value, 'nucleus') + + +def _prepare_cellaligner_image(cell_object, channel, center=None, processing_info=None): + if isinstance(cell_object, str): + with open(cell_object, 'rb') as file: + cell_object = pickle.load(file) + + if channel is None: + raise ValueError('channel must be provided for CellAligner_Cell inputs') + + if center is None: + center = processing_info.get('center', 'cell') if processing_info is not None else 'cell' + + cell_image = make_cell_image(cell_object, ['nucleus', channel]) + cell_image = to_shape(cell_image, (max(cell_image.shape[:2]), max(cell_image.shape[:2]), 3)) + cell_image = cell_image[:, :, [2, 0, 1]] + cell_image = align_image(cell_image, center=center) + + if processing_info is not None: + if not processing_info.get('rescale', True): + max_size = processing_info.get('max_size') + if max_size is not None: + cell_image = to_shape(cell_image, (max_size, max_size, 3)) + + shape = processing_info.get('shape') + if shape is not None: + cell_image = resize_cell_image(cell_image, shape) + + return cell_image + + +def _load_unique_paired_dataset_images(paired_dataset): + unique_indices = sorted({index for pair in paired_dataset.image_pairs for index in pair}) + images = [np.load(os.path.join(paired_dataset.image_dir, f'cell_{index}.npy')) for index in unique_indices] + return unique_indices, images + + +def _paired_dataset_to_indexed_image_dataset(paired_dataset): + unique_indices = sorted({index for pair in paired_dataset.image_pairs for index in pair}) + transform = getattr(paired_dataset, 'transform', None) + indexed_dataset = IndexedImageDataset(paired_dataset.image_dir, unique_indices, transform=transform) + return unique_indices, indexed_dataset + + +def _batch_to_tensor(batch, device): + if isinstance(batch, (list, tuple)): + batch = batch[0] + + if isinstance(batch, torch.Tensor): + tensor = batch + if tensor.ndim == 4 and tensor.shape[-1] in [1, 3] and tensor.shape[1] not in [1, 3]: + tensor = tensor.permute(0, 3, 1, 2).contiguous() + elif tensor.ndim == 3 and tensor.shape[-1] in [1, 3]: + tensor = tensor.permute(2, 0, 1).unsqueeze(0).contiguous() + else: + array = np.asarray(batch) + if array.ndim == 3: + array = array[np.newaxis, ...] + if array.shape[-1] in [1, 3]: + tensor = torch.from_numpy(array).permute(0, 3, 1, 2) + else: + tensor = torch.from_numpy(array) + + if tensor.ndim == 3: + tensor = tensor.unsqueeze(0) + + if tensor.dtype != torch.float32: + tensor = tensor.float() + + return tensor.to(device) + + +def _as_sequence_if_object_array(value): + if isinstance(value, np.ndarray) and value.dtype == object: + return value.tolist() + return value + +def resize_cell_image(image, target_shape): + """ + Resize a 3-channel cell image with appropriate interpolation for each channel. + + Uses bilinear interpolation for probability channels and nearest neighbor + for binary mask channels to preserve the discrete nature of segmentation masks. + + :param image: Input image of shape (H, W, 3) where channel 0 is probability/intensity, + channels 1 and 2 are binary masks. + :type image: numpy.ndarray + :param target_shape: Target shape as (height, width) for the resized image. + :type target_shape: tuple of int + :returns: Resized image of shape (target_shape[0], target_shape[1], 3) with + appropriate interpolation applied to each channel. + :rtype: numpy.ndarray + """ + out = np.zeros((target_shape[0], target_shape[1], image.shape[2]), dtype=image.dtype) + # Probability channel (bilinear) + out[:,:,0] = ski.transform.resize(image[:,:,0], target_shape, order=1, preserve_range=True, anti_aliasing=True) + # Binary channels (nearest neighbor) + out[:,:,1] = ski.transform.resize(image[:,:,1], target_shape, order=0, preserve_range=True, anti_aliasing=False) + out[:,:,2] = ski.transform.resize(image[:,:,2], target_shape, order=0, preserve_range=True, anti_aliasing=False) + return out + + +def major_axis_pca_with_center(mask, center): + """ + Compute the major axis of a binary mask using PCA with a specified center point. + + Performs principal component analysis on the mask pixels relative to a given + center point to determine the major axis of the shape. The axis orientation + is normalized to point toward the side with more mass distribution. + + :param mask: 2D binary mask where non-zero values indicate the region of interest. + :type mask: numpy.ndarray + :param center: Center point as (y, x) coordinates to use for PCA computation + (e.g., nucleus centroid). + :type center: tuple of float + :returns: Tuple containing the major axis unit vector and the angle in radians. + :rtype: tuple (major_axis_vector: numpy.ndarray, angle: float) + """ + yx = np.argwhere(mask > 0) + yx_centered = yx - np.array(center) # center at nucleus + if len(yx_centered) < 2: + return np.array([1, 0]), 0.0 # default axis + cov = np.cov(yx_centered, rowvar=False) + eigvals, eigvecs = np.linalg.eig(cov) + major_axis = eigvecs[:, np.argmax(eigvals)] + # Ensure consistent orientation: major axis points toward side with more mass + projections = yx_centered @ major_axis + if projections.sum() < 0: + major_axis = -major_axis + angle = np.arctan2(major_axis[1], major_axis[0]) # angle w.r.t. x-axis + return major_axis, angle + + +def align_image(image, center='cell', cell_mask_channel=1, nucleus_channel=2): + """ + Center and align a 3-channel cell image based on morphological features. + + Centers the image based on the centroid of the largest labeled object and + rotates it to align the major axis horizontally. The image is padded as + needed and trimmed to remove empty borders. + + :param image: Input image of shape (H, W, 3) containing cell imaging data. + :type image: numpy.ndarray + :param center: Centering method: 'cell' to center on cell mask, 'nucleus' to center + on nucleus mask. Default is 'cell'. + :type center: str + :param cell_mask_channel: Index of the cell mask channel. Default is 1. + :type cell_mask_channel: int + :param nucleus_channel: Index of the nucleus mask channel. Default is 2. + :type nucleus_channel: int + :returns: Centered and rotated image, possibly larger than input due to padding + and rotation operations. + :rtype: numpy.ndarray + """ + # Center based on largest labeled cell/nucleus object + if center == 'cell': + mask = image[..., cell_mask_channel] + elif center == 'nucleus': + mask = image[..., nucleus_channel] + else: + raise ValueError("center must be 'cell' or 'nucleus'") + labeled_objects = ski.measure.label(mask > 0) + object_regions = ski.measure.regionprops(labeled_objects) + if not object_regions: + print(f'No {center}s found, returning original image.') + return image.copy() + largest_object = max(object_regions, key=lambda x: x.area) + cy, cx = largest_object.centroid # (y, x) + h, w = image.shape[:2] + pad_top = int(max(0, (h - cy) - cy)) + pad_bottom = int(max(0, cy - (h - cy))) + pad_left = int(max(0, (w - cx) - cx)) + pad_right = int(max(0, cx - (w - cx))) + padded = np.pad(image, ((pad_top, pad_bottom), (pad_left, pad_right), (0,0)), mode='constant') + # pad image to make it square + if padded.shape[0] != padded.shape[1]: + size = max(padded.shape[:2]) + pad_h = (size - padded.shape[0]) // 2 + pad_w = (size - padded.shape[1]) // 2 + padded = np.pad(padded, ((pad_h, size-padded.shape[0]-pad_h), (pad_w, size-padded.shape[1]-pad_w), (0,0)), mode='constant') + # add extra padding to ensure mask is not cropped after rotation + pad_extra = int(padded.shape[0] / 2 * (np.sqrt(2) - 1)) + padded = np.pad(padded, ((pad_extra, pad_extra), (pad_extra, pad_extra), (0,0)), mode='constant') + # New centroid after padding + new_cy = padded.shape[0]//2 + new_cx = padded.shape[1]//2 + # Rotation based on largest labeled cell object + cell_mask = padded[..., cell_mask_channel] + # Use centroid of largest nucleus for centering, but largest cell for rotation + major_axis, angle = major_axis_pca_with_center(cell_mask, (new_cy, new_cx)) + angle_deg = -np.degrees(angle) + rotated_channels = [] + for c in range(padded.shape[2]): + order = 0 if np.array_equal(np.unique(padded[...,c]), [0,1]) else 1 + rotated = ski.transform.rotate(padded[...,c], angle=angle_deg, center=(new_cy, new_cx), order=order, preserve_range=True) + rotated_channels.append(rotated) + result = np.stack(rotated_channels, axis=-1) + # trim empty borders + mask = result[..., cell_mask_channel] + if np.any(mask > 0): + min_y, min_x = np.array(np.where(mask > 0)).min(axis=1) + max_y, max_x = np.array(np.where(mask > 0)).max(axis=1) + trim_len = np.min([min_y, min_x, result.shape[0]-max_y, result.shape[1]-max_x]) + if trim_len > 0: + result = result[trim_len:result.shape[0]-trim_len, trim_len:result.shape[1]-trim_len, :] + return result + + +def make_NN_training_data(save_path, cell_objects, reference_cell_object, mapped_channel_distributions, channel, center='cell', rescale=True, shape=(64, 64)): + """ + Generate training data from CellAligner_Cell objects for neural network training. + + Creates paired cell images and their corresponding mapped versions for training + deep learning models. Images are aligned, normalized, and saved as numpy arrays. + + :param save_path: Directory path to save processed images. Cell images saved to + '/cell_images' and mapped images to '/mapped_cell_images'. + :type save_path: str + :param cell_objects: List of CellAligner_Cell objects or paths to pickled CellAligner_Cell objects. + :type cell_objects: list + :param reference_cell_object: Reference cell object or path to pickled reference cell object used + as template for mapped distributions. + :type reference_cell_object: CellAligner_Cell or str + :param mapped_channel_distributions: Array of mapped protein distributions for each cell. + :type mapped_channel_distributions: numpy.ndarray + :param channel: Channel name to use for image processing. + :type channel: str + :param center: Centering method for image alignment: 'cell' or 'nucleus'. Default is 'cell'. + :type center: str + :param rescale: Whether to rescale images to a fixed size. Default is True. + :type rescale: bool + :param shape: Target shape (height, width) for resizing images. Default is (64, 64). + :type shape: tuple of int + :returns: None. Images are saved to disk as .npy files. + :rtype: None + """ + max_size = None + if isinstance(reference_cell_object, str): + with open(reference_cell_object, 'rb') as file: + reference_cell_object = pickle.load(file) + + if not rescale: + max_size = 0 + for cell_object in cell_objects: + # Load CellAligner_Cell object if path specified + if isinstance(cell_object, str): + with open(cell_object, 'rb') as file: + cell_object = pickle.load(file) + cell_image = make_cell_image(cell_object, ['nucleus', channel]) + cell_image = to_shape(cell_image, (max(cell_image.shape[:2]), max(cell_image.shape[:2]), 3)) + cell_image = cell_image[:,:,[2,0,1]] + cell_image = align_image(cell_image, center=center) + max_size = max(max_size, max(cell_image.shape[:2])) + + for i, cell_object in enumerate(cell_objects): + # Load CellAligner_Cell object if path specified + if isinstance(cell_object, str): + pickle.load(open(cell_object, 'rb')) + # make image array from cell object + cell_image = make_cell_image(cell_object, ['nucleus', channel]) + mapped_cell_object = reference_cell_object.copy() + mapped_cell_object.intensities[channel] = mapped_channel_distributions[i] + mapped_cell_image = make_cell_image(mapped_cell_object, ['nucleus', channel]) + # pad image to square + cell_image = to_shape(cell_image, (max(cell_image.shape[:2]), max(cell_image.shape[:2]), 3)) + mapped_cell_image = to_shape(mapped_cell_image, (max(mapped_cell_image.shape[:2]), max(mapped_cell_image.shape[:2]), 3)) + # reorder channels: channel, binary cell mask, binary nucleus mask + cell_image = cell_image[:,:,[2,0,1]] + mapped_cell_image = mapped_cell_image[:,:,[2,0,1]] + # align image + cell_image = align_image(cell_image, center=center) + mapped_cell_image = align_image(mapped_cell_image, center=center) + # resize image + if not rescale: + cell_image = to_shape(cell_image, (max_size, max_size, 3)) + mapped_cell_image = to_shape(mapped_cell_image, (max_size, max_size, 3)) + cell_image = resize_cell_image(cell_image, shape) + mapped_cell_image = resize_cell_image(mapped_cell_image, shape) + # make cell_images & mapped_cell_images directories if they don't exist + if not os.path.exists(os.path.join(save_path, 'cell_images')): + os.makedirs(os.path.join(save_path, 'cell_images')) + if not os.path.exists(os.path.join(save_path, 'mapped_cell_images')): + os.makedirs(os.path.join(save_path, 'mapped_cell_images')) + # save image + np.save(os.path.join(save_path, 'cell_images', f'cell_{i}.npy'), cell_image) + np.save(os.path.join(save_path, 'mapped_cell_images', f'mapped_cell_{i}.npy'), mapped_cell_image) + + _write_cell_image_processing_info(save_path, max_size=max_size, rescale=rescale, shape=shape, center=center) + + +class EfficientNetFeatureExtractor(nn.Module): + """ + Feature extractor using EfficientNet backbone for cell image embeddings. + + Adapts a pretrained EfficientNet model to extract fixed-size feature + embeddings from cell images. Handles variable input channel numbers + and resizes inputs to match EfficientNet requirements. + + :param embedding_size: Size of the output embedding vector. Default is 50. + :type embedding_size: int, optional + :param input_channels: Number of input channels in the cell images. Default is 3. + :type input_channels: int, optional + :param efficientnet_type: Type of EfficientNet architecture to use. Default is 'efficientnet_b0'. + :type efficientnet_type: str, optional + :param pretrained: Whether to use pretrained ImageNet weights. Default is True. + :type pretrained: bool, optional + """ + def __init__(self, embedding_size=50, input_channels=3, efficientnet_type='efficientnet_b0', pretrained=True): + super().__init__() + # Load a pretrained EfficientNet + efficientnet = getattr(models, efficientnet_type)(pretrained=pretrained) + # Determine expected input size from model metadata if available + if hasattr(efficientnet, 'default_cfg') and 'input_size' in efficientnet.default_cfg: + self.efficientnet_input_size = efficientnet.default_cfg['input_size'][-1] + else: + self.efficientnet_input_size = 224 # Fallback for older torchvision + if input_channels != 3: + efficientnet.features[0][0] = nn.Conv2d(input_channels, efficientnet.features[0][0].out_channels, + kernel_size=efficientnet.features[0][0].kernel_size, + stride=efficientnet.features[0][0].stride, + padding=efficientnet.features[0][0].padding, + bias=False) + self.features = efficientnet.features + self.avgpool = efficientnet.avgpool + self.fc = nn.Linear(efficientnet.classifier[1].in_features, embedding_size) + + def forward(self, x): + """ + Forward pass through the feature extractor. + + Parameters + ---------- + x : torch.Tensor + Input tensor of shape (batch_size, channels, height, width). + + Returns + ------- + torch.Tensor + Feature embedding tensor of shape (batch_size, embedding_size). + """ + # Always resize input to expected EfficientNet input size + if x.shape[2] != self.efficientnet_input_size or x.shape[3] != self.efficientnet_input_size: + x = F.interpolate(x, size=(self.efficientnet_input_size, self.efficientnet_input_size), mode='bilinear', align_corners=False) + x = self.features(x) + x = self.avgpool(x) + x = torch.flatten(x, 1) + x = self.fc(x) + return x + + +class UNetDecoder(nn.Module): + """ + U-Net style decoder for reconstructing images from feature embeddings. + + Implements a decoder network that reconstructs images from compressed + feature embeddings using transposed convolutions and skip connections. + The architecture progressively upsamples from a compact representation + back to full image resolution. + + :param embedding_size: Size of the input embedding vector. Default is 50. + :type embedding_size: int, optional + :param image_size: Target output image size (assumed square). Default is 64. + :type image_size: int, optional + :param out_channels: Number of output channels in the reconstructed image. Default is 1. + :type out_channels: int, optional + """ + def __init__(self, embedding_size=50, image_size=64, out_channels=1): + super().__init__() + self.image_size = image_size + self.fc = nn.Linear(embedding_size, 128 * (image_size // 8) * (image_size // 8)) + # Encoder/decoder blocks + self.up1 = nn.ConvTranspose2d(128, 64, kernel_size=2, stride=2) + self.conv1 = nn.Sequential( + nn.Conv2d(64, 64, 3, padding=1), nn.ReLU(), + nn.Conv2d(64, 64, 3, padding=1), nn.ReLU() + ) + self.up2 = nn.ConvTranspose2d(64, 32, kernel_size=2, stride=2) + self.conv2 = nn.Sequential( + nn.Conv2d(32, 32, 3, padding=1), nn.ReLU(), + nn.Conv2d(32, 32, 3, padding=1), nn.ReLU() + ) + self.up3 = nn.ConvTranspose2d(32, 16, kernel_size=2, stride=2) + self.conv3 = nn.Sequential( + nn.Conv2d(16, 16, 3, padding=1), nn.ReLU(), + nn.Conv2d(16, 16, 3, padding=1), nn.ReLU() + ) + self.final = nn.Conv2d(16, out_channels, 1) + + def forward(self, x): + """ + Forward pass through the U-Net decoder. + + Parameters + ---------- + x : torch.Tensor + Input embedding tensor of shape (batch_size, embedding_size). + + Returns + ------- + torch.Tensor + Reconstructed image tensor of shape (batch_size, out_channels, + image_size, image_size) with softmax-normalized probability + distributions. + """ + # x: (batch, embedding_size) + x = self.fc(x) + x = x.view(x.size(0), 128, self.image_size // 8, self.image_size // 8) + x = self.up1(x) + x = self.conv1(x) + x = self.up2(x) + x = self.conv2(x) + x = self.up3(x) + x = self.conv3(x) + x = self.final(x) + # Output: (batch, out_channels, H, W) + x = torch.softmax(x.view(x.size(0), -1), dim=1).view(x.size(0), 1, self.image_size, self.image_size) + return x + + +class dCellAlignerNetwork(nn.Module): + """ + Deep CellAligner Network. + + A complete neural network architecture that combines feature extraction, + distance computation, and image reconstruction in a multi-task learning + framework. Designed for approximating CellAligner mapping to anchor cell + morphologies and learning embeddings that preserve metrics for quantifying + differences in protein localization after mapping. + + :param input_channels: Number of input image channels. Default is 3. + :type input_channels: int, optional + :param embedding_size: Dimensionality of the feature embedding space. Default is 50. + :type embedding_size: int, optional + :param image_size: Size of input/output images (assumed square). Default is 64. + :type image_size: int, optional + """ + def __init__(self, input_channels=3, embedding_size=50, image_size=64): + super().__init__() + self.feature_extractor = EfficientNetFeatureExtractor( + embedding_size=embedding_size, + input_channels=input_channels, + efficientnet_type='efficientnet_b4', + pretrained=True + ) + # Only decode the first/protein/probability channel + self.feature_decoder = UNetDecoder(embedding_size, image_size) + + def forward(self, x1, x2, return_embedding=False): + """ + Forward pass through the complete Deep CellAligner network. + + Processes two input images through feature extraction, computes their + embedding distance, and reconstructs both images. Optionally returns + the intermediate feature embeddings. + + Parameters + ---------- + x1, x2 : torch.Tensor + Input image tensors of shape (batch_size, channels, height, width). + return_embedding : bool, optional + Whether to return intermediate feature embeddings. Default is False. + + Returns + ------- + tuple + If return_embedding is False: + distance : torch.Tensor + Squared Euclidean distance between embeddings of shape + (batch_size, 1). + uf1, uf2 : torch.Tensor + Reconstructed images of shape (batch_size, 1, height, width). + + If return_embedding is True: + distance : torch.Tensor + Squared Euclidean distance between embeddings. + uf1, uf2 : torch.Tensor + Reconstructed images. + feat1, feat2 : torch.Tensor + Feature embeddings of shape (batch_size, embedding_size). + """ + # Extract features from both images + feat1 = self.feature_extractor(x1) + feat2 = self.feature_extractor(x2) + # Compute Euclidean distance in embedding space + distance = torch.sum((feat1 - feat2) ** 2, dim=1, keepdim=True) + # Reconstruct both images from their embeddings + uf1 = self.feature_decoder(feat1) + uf2 = self.feature_decoder(feat2) + # Return: distance, reconstruction1, reconstruction2, copy1, copy2 + if return_embedding: + return distance, uf1, uf2, feat1, feat2 + else: + return distance, uf1, uf2 + + +class PretrainPairedDataset(Dataset): + """ + PyTorch Dataset for pretraining with paired input and target images. + + Loads pairs of numpy arrays for pretraining tasks where each input image + has a corresponding target image. Handles channel dimension reordering + and applies optional transforms. + + :param input_files: List of file paths to input image numpy arrays. + :type input_files: list of str + :param target_files: List of file paths to target image numpy arrays. Must have same + length as input_files. + :type target_files: list of str + :param transform: Optional transform to apply to both input and target images. + Default is None. + :type transform: callable, optional + :param augment_transform: Additional augmentation transform for data augmentation. Default is None. + :type augment_transform: callable, optional + + :raises AssertionError: If input_files and target_files have different lengths. + """ + def __init__(self, input_files, target_files, transform=None, augment_transform=None, n_augment=1): + self.input_files = [f for f in input_files for _ in range(n_augment)] + self.target_files = [f for f in target_files for _ in range(n_augment)] + assert len(self.input_files) == len(self.target_files), 'Input and target directories must have the same number of images.' + self.transform = transform + self.augment_transform = augment_transform + + def __len__(self): + return len(self.input_files) + + def __getitem__(self, idx): + input_img = np.load(self.input_files[idx]) + target_img = np.load(self.target_files[idx]) + # Ensure shape is (C, H, W) for torch + if input_img.ndim == 2: + input_img = input_img[np.newaxis, ...] + elif input_img.ndim == 3 and input_img.shape[0] != 3 and input_img.shape[-1] == 3: + input_img = np.transpose(input_img, (2, 0, 1)) + if target_img.ndim == 2: + target_img = target_img[np.newaxis, ...] + elif target_img.ndim == 3 and target_img.shape[0] != 3 and target_img.shape[-1] == 3: + target_img = np.transpose(target_img, (2, 0, 1)) + if self.transform: + input_img = self.transform(input_img) + target_img = self.transform(target_img) + if self.augment_transform: + input_img = self.augment_transform(input_img) + target_img = self.augment_transform(target_img) + input_img = torch.from_numpy(input_img).float() + target_img = torch.from_numpy(target_img).float() + return input_img, target_img + + +def pretrain_model(paired_dataset, model, dataset_name, save_path=None, + batch_size=64, epochs=10, lr=1e-3, device=None, return_model=True): + """ + Pretrain a model using paired input and target images provided as a PairedDataset. + + This function accepts a `PairedDataset` object and first converts it into a + `PretrainPairedDataset` by collecting the unique image indices referenced in + the paired dataset. For each unique image index `i`, the input path is + '/cell_i.npy' and the target path is + '/mapped_cell_i.npy'. After conversion the rest of the + training loop is identical to the previous implementation. + + :param paired_dataset: Dataset containing paired indices and directory information. Must have + attributes `image_dir`, `mapped_image_dir` and `image_pairs`. + :type paired_dataset: PairedDataset + :param model: Neural network model to pretrain. Must have a forward method that + takes two identical inputs and returns reconstructions. + :type model: torch.nn.Module + :param dataset_name: Name prefix for saved model files. + :type dataset_name: str + :param save_path: Directory path to save the pretrained model. If None, model is not saved. + Default is None. + :type save_path: str, optional + :param batch_size: Batch size for training. Default is 64. + :type batch_size: int, optional + :param epochs: Number of training epochs. Default is 10. + :type epochs: int, optional + :param lr: Learning rate for the Adam optimizer. Default is 1e-3. + :type lr: float, optional + :param device: Device to run training on. If None, automatically selects GPU + if available. Default is None. + :type device: torch.device, optional + :param return_model: Whether to return the trained model. If False, returns None. + Default is True. + :type return_model: bool, optional + + :returns: The pretrained model if return_model is True, otherwise None. + :rtype: torch.nn.Module or None + """ + # Convert PairedDataset to lists of input/target files (unique images) + if not isinstance(paired_dataset, PairedDataset): + raise TypeError("paired_dataset must be an instance of PairedDataset") + + all_indices = set() + for pair in paired_dataset.image_pairs: + all_indices.update(pair) + all_indices = sorted(list(all_indices)) + + input_files = [os.path.join(paired_dataset.image_dir, f"cell_{idx}.npy") for idx in all_indices] + target_files = [os.path.join(paired_dataset.mapped_image_dir, f"mapped_cell_{idx}.npy") for idx in all_indices] + + dataset = PretrainPairedDataset(input_files, target_files) + loader = DataLoader(dataset, batch_size=batch_size, shuffle=True) + if device is None: + device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') + model = model.to(device) + optimizer = optim.Adam(model.parameters(), lr=lr) + + # Store config for saving (if it's a dCellAlignerNetwork) + config = None + if hasattr(model, 'feature_extractor') and hasattr(model, 'feature_decoder'): + # Try to extract config from dCellAlignerNetwork + try: + input_channels = model.feature_extractor.feature_extractor.features[0][0].in_channels if hasattr(model.feature_extractor, 'feature_extractor') else 3 + embedding_size = model.feature_extractor.fc.out_features + image_size = model.feature_decoder.image_size + config = { + 'input_channels': input_channels, + 'embedding_size': embedding_size, + 'image_size': image_size + } + except: + print("Warning: Could not extract model config for saving") + + # Create save directory if specified + if save_path is not None: + os.makedirs(save_path, exist_ok=True) + + model.train() + best_loss = float('inf') + + for epoch in range(epochs): + total_loss = 0 + progress_bar = tqdm(loader, desc=f"Epoch {epoch+1}/{epochs}") + for input_batch, target_batch in progress_bar: + input_batch = input_batch.float() + target_batch = target_batch.float() + if input_batch.ndim == 4 and input_batch.shape[-1] == 3: + input_batch = input_batch.permute(0, 3, 1, 2) + if target_batch.ndim == 4 and target_batch.shape[-1] == 3: + target_batch = target_batch.permute(0, 3, 1, 2) + input_batch = input_batch.to(device) + target_batch = target_batch.to(device) + optimizer.zero_grad() + _, recon, _ = model(input_batch, input_batch) + # Only reconstruct the first channel (probability/protein) of the target + target = target_batch[:, 0:1, :, :] + pred = recon[:, 0:1, :, :] + loss = kullback_leibler_divergence_loss(target, pred) + loss.backward() + optimizer.step() + total_loss += loss.item() * input_batch.size(0) + progress_bar.set_postfix({'loss': loss.item()}) + + epoch_loss = total_loss / len(dataset) + print(f"Epoch {epoch+1}/{epochs}, Loss: {epoch_loss:.6f}") + + # Save best model if save_path is provided + if save_path is not None and epoch_loss < best_loss: + best_loss = epoch_loss + if config is not None: + # Save with config + torch.save({ + 'state_dict': model.state_dict(), + 'config': config, + 'epoch': epoch + 1, + 'loss': epoch_loss + }, os.path.join(save_path, f'{dataset_name}_pretrained_best.pth')) + else: + # Save without config (backward compatibility) + torch.save(model.state_dict(), os.path.join(save_path, f'{dataset_name}_pretrained_best.pth')) + print(f' → New best model saved (loss: {epoch_loss:.6f})') + + # Save final model if save_path is provided + if save_path is not None: + if config is not None: + torch.save({ + 'state_dict': model.state_dict(), + 'config': config, + 'epoch': epochs, + 'loss': epoch_loss + }, os.path.join(save_path, f'{dataset_name}_pretrained_final.pth')) + else: + torch.save(model.state_dict(), os.path.join(save_path, f'{dataset_name}_pretrained_final.pth')) + print(f'Saved pretrained model to {save_path}/{dataset_name}_pretrained_final.pth') + + return model if return_model else None + + +def kullback_leibler_divergence_loss(y_true, y_pred): + """ + Compute Kullback-Leibler divergence loss for probability distributions. + + Measures how well a predicted probability distribution matches a target + distribution. Used for reconstruction quality assessment when dealing + with normalized protein distributions. + + :param y_true: Target probability distribution tensor. + :type y_true: torch.Tensor + :param y_pred: Predicted probability distribution tensor. + :type y_pred: torch.Tensor + :returns: Mean KL divergence loss across the batch. + :rtype: torch.Tensor + + .. note:: + + The KL divergence is computed as: KL(P||Q) = sum(P * log(P/Q)) + Values are clamped to avoid log(0) numerical issues. + """ + epsilon = 1e-8 + + # Clamp values to avoid log(0) + y_true = torch.clamp(y_true, epsilon, 1.0) + y_pred = torch.clamp(y_pred, epsilon, 1.0) + + # Flatten tensors for computation + y_true_flat = y_true.view(y_true.size(0), -1) + y_pred_flat = y_pred.view(y_pred.size(0), -1) + + # Compute KL divergence: KL(P||Q) = sum(P * log(P/Q)) + kl_div = torch.sum(y_true_flat * torch.log(y_true_flat / y_pred_flat), dim=1) + return torch.mean(kl_div) + + +def sparsity_constraint_loss(embeddings, sparsity_target=0.1): + """ + Apply KL divergence sparsity constraint to hidden unit activations. + + Encourages sparse representations by penalizing deviations from a target + sparsity level. This regularization helps prevent overfitting and promotes + more interpretable feature representations. + + :param embeddings: Hidden unit activations of shape (batch_size, embedding_dim). + :type embeddings: torch.Tensor + :param sparsity_target: Desired average activation level for each hidden unit. Default is 0.1. + :type sparsity_target: float, optional + :returns: Scalar sparsity loss computed as the sum of KL divergences across + all embedding dimensions. + :rtype: torch.Tensor + + .. note:: + + Activations are passed through sigmoid to ensure they're in (0,1) range + before computing the sparsity constraint. + """ + epsilon = 1e-8 + # Apply sigmoid to ensure activations are in (0,1) + activations = torch.sigmoid(embeddings) + rho_hat = torch.mean(activations, dim=0) # (embedding_dim,) + rho = torch.full_like(rho_hat, sparsity_target) + kl = rho * torch.log((rho + epsilon) / (rho_hat + epsilon)) + \ + (1 - rho) * torch.log((1 - rho + epsilon) / (1 - rho_hat + epsilon)) + return torch.sum(kl) + + +def reconstruction_loss(x, uf): + """ + Compute multi-channel reconstruction loss for cell images. + + Applies appropriate loss functions for different channel types: + probability distributions use KL divergence, while binary masks + use binary cross-entropy loss. + + :param x: Original image tensor of shape (batch_size, 3, height, width). + :type x: torch.Tensor + :param uf: Reconstructed image tensor of same shape as x. + :type uf: torch.Tensor + :returns: Combined reconstruction loss across all channels. + :rtype: torch.Tensor + + .. note:: + + - Channel 0: Probability distribution (KL divergence loss) + - Channel 1: Binary cell mask (Binary cross-entropy loss) + - Channel 2: Binary nucleus mask (Binary cross-entropy loss) + """ + # Split channels + x_prob, x_mask1, x_mask2 = x[:, 0:1, :, :], x[:, 1:2, :, :], x[:, 2:3, :, :] + uf_prob, uf_mask1, uf_mask2 = uf[:, 0:1, :, :], uf[:, 1:2, :, :], uf[:, 2:3, :, :] + + # Probability channel: KL divergence + kl = kullback_leibler_divergence_loss(x_prob, uf_prob) + # Binary mask channels: BCE loss + bce1 = F.binary_cross_entropy(uf_mask1, x_mask1) + bce2 = F.binary_cross_entropy(uf_mask2, x_mask2) + return kl + bce1 + bce2 + + +def get_random_pairs(indices, n_pairs): + """ + Generate a random subset of unique pairs from a list of indices. + + Creates all possible unique pairs from the input indices and randomly + samples a specified number of them. Useful for creating training pairs + from a dataset without exhaustive pairwise combinations. + + :param indices: List or array of indices to create pairs from. + :type indices: array-like + :param n_pairs: Number of pairs to randomly sample. If larger than the total + possible pairs, returns all possible pairs. + :type n_pairs: int + :returns: Array of shape (n_pairs, 2) containing randomly selected index pairs. + :rtype: numpy.ndarray + """ + all_pairs = np.array(list(it.combinations(indices, 2))) + if n_pairs > len(all_pairs): + n_pairs = len(all_pairs) + pair_inds = np.random.choice(len(all_pairs), n_pairs, replace=False) + return all_pairs[pair_inds] + + +class IndexedImageDataset(Dataset): + """ + PyTorch Dataset for loading individual cell images by index. + + Simple dataset for loading cell images by index, useful for extracting + embeddings from unique images in a PairedDataset without loading duplicates. + + :param image_dir: Path to directory containing cell image .npy files with naming + convention 'cell_{index}.npy'. + :type image_dir: str + :param indices: List of cell indices to load. + :type indices: list of int + :param transform: Transform function to apply to all images. Default is None. + :type transform: callable, optional + """ + def __init__(self, image_dir, indices, transform=None): + self.image_dir = image_dir + self.indices = indices + self.transform = transform + + def __len__(self): + return len(self.indices) + + def __getitem__(self, idx): + index = self.indices[idx] + img_path = os.path.join(self.image_dir, f"cell_{index}.npy") + image = np.load(img_path) + + if self.transform is not None: + image = self.transform(image) + + return image + + +class PairedDataset(Dataset): + """ + PyTorch Dataset for loading paired cell images with distance labels. + + Loads pairs of cell images and their mapped counterparts from numpy files + for training distance-based models. Supports data augmentation and lazy + loading for memory efficiency. + + :param image_dir: Path to directory containing cell image .npy files with naming + convention 'cell_{index}.npy'. + :type image_dir: str + :param mapped_image_dir: Path to directory containing mapped cell image .npy files with naming + convention 'mapped_cell_{index}.npy'. + :type mapped_image_dir: str + :param distances: Distance values corresponding to each image pair for supervised learning. + :type distances: list of float + :param image_pairs: List of (index1, index2) tuples specifying which images to pair. + :type image_pairs: list of tuple + :param transform: Transform function to apply to all images. Default is None. + :type transform: callable, optional + :param augment_transform: Additional augmentation transform for data augmentation. Default is None. + :type augment_transform: callable, optional + :param n_augment: Number of augmented copies to create for each pair. Default is 1. + :type n_augment: int, optional + + .. note:: + + The dataset expects file naming conventions: + - Cell images: 'cell_{index}.npy' + - Mapped images: 'mapped_cell_{index}.npy' + """ + def __init__(self, image_dir, mapped_image_dir, distances, image_pairs, transform=None, augment_transform=None, n_augment=1): + # Store directory paths. Listing all files is no longer needed. + self.image_dir = image_dir + self.mapped_image_dir = mapped_image_dir + + # The rest of the logic remains the same + self.distances = [distance for distance in distances for _ in range(n_augment)] + self.image_pairs = [image_pair for image_pair in image_pairs for _ in range(n_augment)] + self.transform = transform + self.augment_transform = augment_transform + + def __getitem__(self, index): + # Get the integer indices for the pair + ind_1, ind_2 = self.image_pairs[index] + + # Dynamically construct the filenames using the indices + img_path_1 = os.path.join(self.image_dir, f"cell_{ind_1}.npy") + img_path_2 = os.path.join(self.image_dir, f"cell_{ind_2}.npy") + mapped_img_path_1 = os.path.join(self.mapped_image_dir, f"mapped_cell_{ind_1}.npy") + mapped_img_path_2 = os.path.join(self.mapped_image_dir, f"mapped_cell_{ind_2}.npy") + + # Load the NumPy arrays from the .npy files + image_1 = np.load(img_path_1) + image_2 = np.load(img_path_2) + mapped_image_cell_1 = np.load(mapped_img_path_1) + mapped_image_cell_2 = np.load(mapped_img_path_2) + + # Apply the general transform if it exists + if self.transform is not None: + image_1 = self.transform(image_1) + image_2 = self.transform(image_2) + mapped_image_cell_1 = self.transform(mapped_image_cell_1) + mapped_image_cell_2 = self.transform(mapped_image_cell_2) + + # Apply the augmentation transform if it exists + if self.augment_transform is not None: + image_1 = self.augment_transform(image_1) + image_2 = self.augment_transform(image_2) + + distance = self.distances[index] + + return image_1, image_2, mapped_image_cell_1, mapped_image_cell_2, distance + + def __len__(self): + # The length is determined by the number of pairs, which is correct + return len(self.image_pairs) + + +class RandomHorizontalRescale(object): + """ + Data augmentation transform that randomly rescales image width. + + Randomly rescales the horizontal axis of cell images to achieve uniform + distribution of cell mask widths. Uses appropriate interpolation methods + for different channel types (bilinear for intensity, nearest for masks). + + :param min_relative_width: Minimum relative width of the cell mask as fraction of image width. + Default is 0.1. + :type min_relative_width: float, optional + :param max_relative_width: Maximum relative width of the cell mask as fraction of image width. + Default is 1.0. + :type max_relative_width: float, optional + + .. note:: + + - Channel 0: Resized with bilinear interpolation (intensity/probability) + - Other channels: Resized with nearest neighbor interpolation (binary masks) + The transform maintains the original image width by padding or cropping after rescaling. + """ + def __init__(self, min_relative_width=0.1, max_relative_width=1.0): + assert 0 < min_relative_width <= max_relative_width <= 1.0 + self.min_relative_width = min_relative_width + self.max_relative_width = max_relative_width + + def __call__(self, image): + c, h, w = image.shape + mask = image[1] # channel 1 is the segmentation mask + mask_inds = (mask > 0).nonzero(as_tuple=False) + min_x = mask_inds[:, 1].min().item() + max_x = mask_inds[:, 1].max().item() + mask_width = max_x - min_x + 1 + + # Compute allowed min/max mask widths in pixels + min_mask_width = int(self.min_relative_width * w) + max_mask_width = int(self.max_relative_width * w) + min_mask_width = max(1, min_mask_width) + max_mask_width = max(min_mask_width, max_mask_width) + + # Sample target mask width + target_mask_width = random.randint(min_mask_width, max_mask_width) + scale = target_mask_width / mask_width + + # Compute new width for the whole image + new_w = int(round(w * scale)) + new_w = max(1, new_w) + + # Resize each channel separately, with bilinear for channel 0, nearest for others + resized = [] + for i in range(c): + channel = image[i].unsqueeze(0) + if i == 0: + resized_channel = TF.resize(channel, [h, new_w], interpolation=TF.InterpolationMode.BILINEAR) + else: + resized_channel = TF.resize(channel, [h, new_w], interpolation=TF.InterpolationMode.NEAREST) + resized.append(resized_channel.squeeze(0)) + image_rescaled = torch.stack(resized, dim=0) + + # Pad or crop to original width + if new_w < w: + pad = (w - new_w) // 2 + image_rescaled = TF.pad(image_rescaled, (pad, 0, w - new_w - pad, 0)) + elif new_w > w: + crop = (new_w - w) // 2 + image_rescaled = image_rescaled[:, :, crop:crop + w] + return image_rescaled + + +def train_dCellAligner(train_dataset, valid_dataset, test_dataset, save_path, dataset_name, embedding_size=50, + image_shape=(64,64), batch_size=100, epochs=100, + device=None, learning_rate=0.001, dist_weight=1.0, + early_stopping=True, patience=3, weight_decay=1e-5, + lr_gamma=0.95, sparsity_weight=0.0, sparsity_target=0.05, + pretrained_path=None, show_loss_components=False): + """ + Trains a Deep CellAligner model using multi-task learning with distance prediction + and image reconstruction objectives. Supports early stopping, learning rate + scheduling, and optional sparsity constraints. + + :param train_dataset: PyTorch datasets for training, validation, and testing. + :type train_dataset: Dataset + :param valid_dataset: Validation dataset. + :type valid_dataset: Dataset + :param test_dataset: Test dataset. + :type test_dataset: Dataset + :param save_path: Directory path to save the trained model and checkpoints. + :type save_path: str + :param dataset_name: Name prefix for saved model files. + :type dataset_name: str + :param embedding_size: Dimensionality of the feature embedding space. Default is 50. + :type embedding_size: int, optional + :param image_shape: Shape of input images as (height, width). Default is (64, 64). + :type image_shape: tuple of int, optional + :param batch_size: Batch size for training. Default is 100. + :type batch_size: int, optional + :param epochs: Maximum number of training epochs. Default is 100. + :type epochs: int, optional + :param device: Device for training. If None, automatically selects GPU if available. + :type device: torch.device, optional + :param learning_rate: Initial learning rate for Adam optimizer. Default is 0.001. + :type learning_rate: float, optional + :param dist_weight: Weight for distance loss vs reconstruction loss in total loss. Default is 1.0. + :type dist_weight: float, optional + :param early_stopping: Whether to use early stopping based on validation loss. Default is True. + :type early_stopping: bool, optional + :param patience: Number of epochs to wait for improvement before stopping. Default is 3. + :type patience: int, optional + :param weight_decay: L2 regularization weight for optimizer. Default is 1e-5. + :type weight_decay: float, optional + :param lr_gamma: Decay factor for exponential learning rate scheduler. Default is 0.95. + :type lr_gamma: float, optional + :param sparsity_weight: Weight for sparsity constraint loss. Default is 0.0 (disabled). + :type sparsity_weight: float, optional + :param sparsity_target: Target sparsity level for hidden activations. Default is 0.05. + :type sparsity_target: float, optional + :param pretrained_path: Path to pretrained model weights to initialize from. Default is None. + :type pretrained_path: str, optional + :param show_loss_components: Whether to display individual loss components (distance, reconstruction, + sparsity) during training. Default is False. + :type show_loss_components: bool, optional + + :returns: Tuple containing the trained model, training loss history, and validation loss history. + :rtype: tuple (model: torch.nn.Module, train_losses: list of float, val_losses: list of float) + """ + # Setup device + if device is None: + device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') + print(f"Training on device: {device}") + + train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True) + valid_loader = DataLoader(valid_dataset, batch_size=batch_size, shuffle=False) + test_loader = DataLoader(test_dataset, batch_size=batch_size, shuffle=False) + + input_channels = 3 + image_size = image_shape[0] + model = dCellAlignerNetwork(input_channels, embedding_size, image_size).to(device) + + # Store config for saving + config = { + 'input_channels': input_channels, + 'embedding_size': embedding_size, + 'image_size': image_size + } + + # Load pretrained weights if provided + if pretrained_path is not None and os.path.exists(pretrained_path): + print(f"Loading pretrained weights from {pretrained_path}") + checkpoint = torch.load(pretrained_path, map_location=device) + if isinstance(checkpoint, dict) and 'state_dict' in checkpoint: + model.load_state_dict(checkpoint['state_dict'], strict=False) + else: + model.load_state_dict(checkpoint, strict=False) + + # Training setup + optimizer = optim.Adam(model.parameters(), lr=learning_rate, weight_decay=weight_decay) + scheduler = optim.lr_scheduler.ExponentialLR(optimizer, gamma=lr_gamma) + best_val_loss = float('inf') + patience_counter = 0 + + # Create model directory + if not os.path.exists(save_path): + os.makedirs(save_path) + + print(f"Starting training for {epochs} epochs...") + + train_losses = [] + val_losses = [] + # Training loop + for epoch in range(epochs): + # Training phase + model.train() + train_loss = 0.0 + train_samples = 0 + train_iter = tqdm(enumerate(train_loader), total=len(train_loader), desc=f"Epoch {epoch+1}/{epochs}", leave=False) + for batch_idx, (x1, x2, mapped_x1, mapped_x2, target_dist) in train_iter: + x1, x2, mapped_x1, mapped_x2, target_dist = x1.to(device), x2.to(device), mapped_x1.to(device), mapped_x2.to(device), target_dist.to(device) + x1 = torch.clamp(x1, 0.0, 1.0) + x2 = torch.clamp(x2, 0.0, 1.0) + mapped_x1 = torch.clamp(mapped_x1, 0.0, 1.0) + mapped_x2 = torch.clamp(mapped_x2, 0.0, 1.0) + optimizer.zero_grad() + # Forward pass (with embeddings) + distance, uf1, uf2, emb1, emb2 = model(x1, x2, return_embedding=True) + dist_loss = F.mse_loss(distance.squeeze(), target_dist) + mapped_x1_prob = mapped_x1[:, 0:1, :, :] + mapped_x2_prob = mapped_x2[:, 0:1, :, :] + kl1 = kullback_leibler_divergence_loss(mapped_x1_prob, uf1) + kl2 = kullback_leibler_divergence_loss(mapped_x2_prob, uf2) + recon_loss = kl1 + kl2 + # KL sparsity constraint + sparsity_loss = sparsity_constraint_loss(emb1, sparsity_target) + sparsity_constraint_loss(emb2, sparsity_target) + total_loss = dist_weight * dist_loss + recon_loss + sparsity_weight * sparsity_loss + total_loss.backward() + optimizer.step() + batch_size_actual = x1.size(0) + train_loss += total_loss.item() * batch_size_actual + train_samples += batch_size_actual + # Validation phase + model.eval() + val_loss = 0.0 + val_samples = 0 + with torch.no_grad(): + for x1, x2, mapped_x1, mapped_x2, target_dist in valid_loader: + x1, x2, mapped_x1, mapped_x2, target_dist = x1.to(device), x2.to(device), mapped_x1.to(device), mapped_x2.to(device), target_dist.to(device) + distance, uf1, uf2 = model(x1, x2) + dist_loss = F.mse_loss(distance.squeeze(), target_dist) + mapped_x1_prob = mapped_x1[:, 0:1, :, :] + mapped_x2_prob = mapped_x2[:, 0:1, :, :] + kl1 = kullback_leibler_divergence_loss(mapped_x1_prob, uf1) + kl2 = kullback_leibler_divergence_loss(mapped_x2_prob, uf2) + recon_loss = kl1 + kl2 + total_loss = dist_weight * dist_loss + recon_loss + batch_size_actual = x1.size(0) + val_loss += total_loss.item() * batch_size_actual + val_samples += batch_size_actual + # Step the learning rate scheduler + scheduler.step() + # Calculate average losses per pair + train_loss /= train_samples + val_loss /= val_samples + train_losses.append(train_loss) + val_losses.append(val_loss) + print(f'Epoch {epoch+1:3d}/{epochs}: Train Loss: {train_loss:.6f}, ' + f'Val Loss: {val_loss:.6f}') + + if show_loss_components: + # check ranges of losses (FOR TESTING) + print(f"Distance Loss: {dist_loss.item()}, Reconstruction Loss: {recon_loss.item()}, Sparsity Loss: {sparsity_loss.item()}") + + # Early stopping and model saving + if val_loss < best_val_loss: + best_val_loss = val_loss + patience_counter = 0 + + # Save best model with config + torch.save({ + 'state_dict': model.state_dict(), + 'config': config + }, f'{save_path}/{dataset_name}_best.pth') + print(f' → New best model saved (val_loss: {val_loss:.6f})') + else: + patience_counter += 1 + if early_stopping and patience_counter >= patience: + print(f'Early stopping at epoch {epoch+1} (patience: {patience})') + break + + # Final test evaluation + print("\nEvaluating on test set...") + model.eval() + test_loss = 0.0 + test_samples = 0 + + with torch.no_grad(): + for x1, x2, mapped_x1, mapped_x2, target_dist in test_loader: + x1, x2, mapped_x1, mapped_x2, target_dist = x1.to(device), x2.to(device), mapped_x1.to(device), mapped_x2.to(device), target_dist.to(device) + distance, uf1, uf2 = model(x1, x2) + dist_loss = F.mse_loss(distance, target_dist) + mapped_x1_prob = mapped_x1[:, 0:1, :, :] + mapped_x2_prob = mapped_x2[:, 0:1, :, :] + kl1 = kullback_leibler_divergence_loss(mapped_x1_prob, uf1) + kl2 = kullback_leibler_divergence_loss(mapped_x2_prob, uf2) + recon_loss = kl1 + kl2 + total_loss = dist_weight * dist_loss + recon_loss + batch_size_actual = x1.size(0) + test_loss += total_loss.item() * batch_size_actual + test_samples += batch_size_actual + + test_loss /= test_samples + print(f'Final Test Loss: {test_loss:.6f}') + + # Save final model with config + print("\nSaving model...") + torch.save({ + 'state_dict': model.state_dict(), + 'config': config + }, f'{save_path}/{dataset_name}_final.pth') + print(f'Saved DWE model to {save_path}/{dataset_name}_final.pth') + + return model, train_losses, val_losses + + +def load_dCellAligner_model(checkpoint_path, device=None): + """ + Load a Deep CellAligner model from a checkpoint containing state dict and config. + + :param checkpoint_path: Path to the checkpoint file containing both state_dict and config. + :type checkpoint_path: str + :param device: Device to load the model on. If None, uses GPU if available. + :type device: torch.device, optional + :returns: Loaded dCellAligner model ready for inference or further training. + :rtype: torch.nn.Module + """ + if device is None: + device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') + + # Load checkpoint + checkpoint = torch.load(checkpoint_path, map_location=device) + + # Handle different checkpoint formats + if isinstance(checkpoint, dict): + if 'config' in checkpoint and 'state_dict' in checkpoint: + # New format with config + config = checkpoint['config'] + state_dict = checkpoint['state_dict'] + + # Create model with saved config + model = dCellAlignerNetwork(**config) + model.load_state_dict(state_dict) + + print(f"Loaded Deep CellAligner model from {checkpoint_path}") + print(f"Config: {config}") + + elif 'state_dict' in checkpoint: + # Old format with just state_dict + print("Warning: Loading checkpoint without config. You'll need to specify model parameters manually.") + return checkpoint['state_dict'] + else: + # Assume checkpoint is a bare state_dict + print("Warning: Loading bare state_dict. You'll need to specify model parameters manually.") + return checkpoint + else: + print("Warning: Unknown checkpoint format.") + return checkpoint + + # Move to device + model = model.to(device) + + return model + + +def extract_embeddings(model, data, batch_size=64, device=None, process_info_path=None, channel=None): + """ + Extract latent embeddings from a trained Deep CellAligner model. + + Processes input data through the feature extractor to obtain latent + embeddings. Supports CellAligner_Cell objects, NumPy arrays of images, + PyTorch datasets, and PairedDatasets as inputs. + + :param model: Trained dCellAligner model with a feature_extractor attribute. + :type model: torch.nn.Module + :param data: Input data to extract embeddings from. Can be: + - PairedDataset + - PyTorch Dataset + - List of CellAligner_Cell objects + - NumPy array with shape (N, H, W, 3) or (H, W, 3) + :type data: list, tuple, numpy.ndarray, torch.utils.data.Dataset, or PairedDataset + :param batch_size: Batch size for processing. Default is 64. + :type batch_size: int, optional + :param device: Device to run computation on. If None, uses model's current device. + :type device: torch.device, optional + :param process_info_path: Path to the saved cell image processing JSON or the directory containing it. + Used when ``data`` contains CellAligner_Cell objects. + :type process_info_path: str or None, optional + :param channel: Channel name to use when ``data`` contains CellAligner_Cell objects or file paths. + :type channel: str or None, optional + :returns: Extracted embeddings of shape (N, embedding_size) where N is the + number of input images. + :rtype: numpy.ndarray + """ + if device is None: + device = next(model.parameters()).device + + model.eval() + embeddings = [] + + data = _as_sequence_if_object_array(data) + + processing_info = _load_cell_image_processing_info(process_info_path) + + if isinstance(data, PairedDataset): + _, data = _paired_dataset_to_indexed_image_dataset(data) + if hasattr(data, '__getitem__') and hasattr(data, '__len__') and isinstance(data, Dataset): + loader = DataLoader(data, batch_size=batch_size, shuffle=False) + with torch.no_grad(): + for batch in tqdm(loader, desc="Extracting embeddings"): + images = _batch_to_tensor(batch, device) + feats = model.feature_extractor(images) + embeddings.append(feats.cpu().numpy()) + return np.concatenate(embeddings, axis=0) + + if isinstance(data, (list, tuple)): + if data and (_is_cellaligner_cell_like(data[0]) or isinstance(data[0], str)): + if process_info_path is None: + raise ValueError("process_info_path must be provided when data contains CellAligner_Cell objects or file paths.") + data = [_prepare_cellaligner_image(cell_object, channel=channel, processing_info=processing_info) for cell_object in data] + data = np.stack(data, axis=0) + else: + raise TypeError('data must be a NumPy array, Dataset, PairedDataset, or a sequence of CellAligner_Cell objects') + + if data.ndim == 3: + data = data[np.newaxis, ...] + + n_images = data.shape[0] + + with torch.no_grad(): + for i in tqdm(range(0, n_images, batch_size), desc="Extracting embeddings"): + batch_end = min(i + batch_size, n_images) + batch_images = data[i:batch_end] + batch_tensor = _batch_to_tensor(batch_images, device) + feats = model.feature_extractor(batch_tensor) + embeddings.append(feats.cpu().numpy()) + + return np.concatenate(embeddings, axis=0) + + +def predict_distances(model, data, batch_size=64, device=None, process_info_path=None, channel=None): + """ + Predict distances from a trained Deep CellAligner model. + + Extracts embeddings for all unique images in the input data and computes + pairwise distances. Supports CellAligner_Cell objects, NumPy arrays of images, + PyTorch datasets, and PairedDatasets as inputs. + + :param model: Trained dCellAligner model with a feature_extractor attribute. + :type model: torch.nn.Module + :param data: Input data to extract embeddings from. Can be: + - PairedDataset + - PyTorch Dataset + - List of CellAligner_Cell objects + - NumPy array with shape (N, H, W, 3) or (H, W, 3) + :type data: PairedDataset, Dataset, numpy.ndarray, list, or tuple + :param batch_size: Batch size for processing embeddings. Default is 64. + :type batch_size: int, optional + :param device: Device to run computation on. If None, uses model's current device. + :type device: torch.device, optional + :param process_info_path: Path to the saved cell image processing JSON or the directory containing it. + Used when ``data`` contains CellAligner_Cell objects. + :type process_info_path: str or None, optional + :param channel: Channel name to use when ``data`` contains CellAligner_Cell objects or file paths. + :type channel: str or None, optional + :returns: If ``data`` is a PairedDataset, returns distances of shape ``(len(data),)`` corresponding + to each pair. Otherwise returns a square distance matrix of shape ``(N, N)`` for the + ``N`` input images, with zeros on the diagonal. + :rtype: numpy.ndarray + """ + if device is None: + device = next(model.parameters()).device + + data = _as_sequence_if_object_array(data) + processing_info = _load_cell_image_processing_info(process_info_path) + + if isinstance(data, PairedDataset): + paired_dataset = data + unique_indices, data = _paired_dataset_to_indexed_image_dataset(paired_dataset) + print(f"Extracting embeddings for {len(unique_indices)} unique images...") + embeddings = extract_embeddings(model, data, batch_size=batch_size, device=device) + + index_to_embedding = {img_idx: emb_idx for emb_idx, img_idx in enumerate(unique_indices)} + predicted_distances = [] + + print(f"Computing distances for {len(paired_dataset.image_pairs)} pairs...") + for pair in tqdm(paired_dataset.image_pairs, desc="Computing pairwise distances"): + idx1, idx2 = pair + emb1 = embeddings[index_to_embedding[idx1]] + emb2 = embeddings[index_to_embedding[idx2]] + predicted_distances.append(np.sum((emb1 - emb2) ** 2)) + + return np.array(predicted_distances) + + if isinstance(data, (list, tuple)) and data and (_is_cellaligner_cell_like(data[0]) or isinstance(data[0], str)): + if process_info_path is None: + raise ValueError("process_info_path must be provided when data contains CellAligner_Cell objects or file paths.") + data = [_prepare_cellaligner_image(cell_object, channel=channel, processing_info=processing_info) for cell_object in data] + data = np.stack(data, axis=0) + + print(f"Extracting embeddings for {len(data)} images...") + embeddings = extract_embeddings(model, data, batch_size=batch_size, device=device) + + if len(embeddings) == 0: + return np.zeros((0, 0), dtype=embeddings.dtype) + + print(f"Computing distance matrix for {len(embeddings)} embeddings:") + diff = embeddings[:, np.newaxis, :] - embeddings[np.newaxis, :, :] + return np.sum(diff ** 2, axis=2) + + +def plot_distance_predictions(model, paired_dataset, batch_size=64, device=None, figsize=(8, 8), + return_plot=False, title=None, alpha=0.6, s=20): + """ + Plot predicted vs true distances for a PairedDataset. + + Creates a scatter plot comparing model predictions against ground truth + distances with a diagonal reference line and correlation metrics. + + :param model: Trained dCellAligner model with a feature_extractor attribute. + :type model: torch.nn.Module + :param paired_dataset: Dataset containing paired images with known distances. + :type paired_dataset: PairedDataset + :param batch_size: Batch size for processing embeddings. Default is 64. + :type batch_size: int, optional + :param device: Device to run computation on. If None, uses model's current device. + :type device: torch.device, optional + :param figsize: Figure size as (width, height). Default is (8, 8). + :type figsize: tuple, optional + :param return_plot: Whether to return the matplotlib figure and axes objects. Default is False. + :type return_plot: bool, optional + :param title: Custom title for the plot. If None, uses default with correlation metrics. + :type title: str, optional + :param alpha: Transparency of scatter points. Default is 0.6. + :type alpha: float, optional + :param s: Size of scatter points. Default is 20. + :type s: int, optional + :returns: If return_plot is False: displays the plot and returns None. + If return_plot is True: returns (fig, ax) matplotlib objects. + :rtype: None or tuple + """ + # Get predictions + predicted_distances = predict_distances(model, paired_dataset, batch_size=batch_size, device=device) + true_distances = np.array(paired_dataset.distances) + + try: + import matplotlib.pyplot as plt + import seaborn as sns + + # Create the plot + fig, ax = plt.subplots(figsize=figsize) + + # Scatter plot + sns.scatterplot(x=true_distances, y=predicted_distances, alpha=alpha, s=s, ax=ax) + + # Add diagonal reference line + min_val = min(true_distances.min(), predicted_distances.min()) + max_val = max(true_distances.max(), predicted_distances.max()) + ax.plot([min_val, max_val], [min_val, max_val], 'r--', alpha=0.8, linewidth=2, label='Perfect prediction') + + # Labels and title + ax.set_xlabel('True Distances') + ax.set_ylabel('Predicted Distances') + + if title: + ax.set_title(title) + + # Grid and legend + ax.grid(True, alpha=0.3) + ax.legend() + + if return_plot: + return fig, ax + else: + plt.show() + return None + + except ImportError: + print("Matplotlib/Seaborn not available for plotting") + return None if not return_plot else (None, None) + + +def plot_reconstruction_comparison(model, paired_dataset, n_cells=5, device=None, figsize=None, seed=None): + """ + Plot comparison of original mapped protein distributions vs model reconstructions. + + Randomly selects cell images from the dataset, shows the original mapped + protein distributions in the top row and their reconstructions from the model in + the bottom row. + + :param model: Trained dCellAligner model with reconstruction capabilities. + :type model: torch.nn.Module + :param paired_dataset: Dataset containing paired images for reconstruction. + :type paired_dataset: PairedDataset + :param n_cells: Number of image pairs to display. Default is 5. + :type n_cells: int, optional + :param device: Device to run model on. If None, uses model's current device. + :type device: torch.device, optional + :param figsize: Figure size as (width, height). If None, automatically calculated + based on number of images. + :type figsize: tuple, optional + :param seed: Random seed for reproducible image selection. Default is None. + :type seed: int, optional + :returns: None. Displays the plot using matplotlib. + :rtype: None + """ + if device is None: + device = next(model.parameters()).device + + # Set random seed if provided + if seed is not None: + np.random.seed(seed) + + # Get unique image indices from the dataset pairs + all_image_indices = set() + for pair in paired_dataset.image_pairs: + all_image_indices.update(pair) + all_image_indices = list(all_image_indices) + + # Randomly select unique image indices + selected_image_indices = np.random.choice(all_image_indices, size=min(n_cells, len(all_image_indices)), replace=False) + + # Set up the plot + if figsize is None: + figsize = (3 * n_cells, 6) + + try: + import matplotlib.pyplot as plt + + fig, axes = plt.subplots(2, n_cells, figsize=figsize) + if n_cells == 1: + axes = axes.reshape(2, 1) + + model.eval() + + with torch.no_grad(): + for i, img_idx in enumerate(selected_image_indices): + # Load the specific image directly by constructing the path + img_path = os.path.join(paired_dataset.image_dir, f"cell_{img_idx}.npy") + mapped_img_path = os.path.join(paired_dataset.mapped_image_dir, f"mapped_cell_{img_idx}.npy") + + # Load the numpy arrays + image = np.load(img_path) + mapped_image = np.load(mapped_img_path) + + # Apply transforms if they exist + if paired_dataset.transform is not None: + image = paired_dataset.transform(image) + mapped_image = paired_dataset.transform(mapped_image) + + # Convert to tensor and add batch dimension + if isinstance(image, np.ndarray): + # Convert from (H, W, C) to (1, C, H, W) + image_tensor = torch.from_numpy(image).permute(2, 0, 1).unsqueeze(0).float().to(device) + else: + # Already a tensor, just add batch dimension and ensure correct device + image_tensor = image.unsqueeze(0).to(device) + + # Get reconstruction from model + _, reconstruction, _ = model(image_tensor, image_tensor) + + # Extract protein channel (channel 0) + if isinstance(mapped_image, np.ndarray): + original_protein = mapped_image[:, :, 0] # Protein channel + else: + original_protein = mapped_image[0].cpu().numpy() # Protein channel + + reconstructed_protein = reconstruction[0, 0].cpu().numpy() # First batch, first (protein) channel + + # Plot original protein (top row) + im1 = axes[0, i].imshow(original_protein, cmap='viridis') + axes[0, i].set_title(f'Original Cell {img_idx}') + axes[0, i].axis('off') + + # Plot reconstructed protein (bottom row) + im2 = axes[1, i].imshow(reconstructed_protein, cmap='viridis') + axes[1, i].set_title(f'Reconstructed Cell {img_idx}') + axes[1, i].axis('off') + + plt.tight_layout() + plt.show() + + except ImportError: + print("Matplotlib not available for plotting") + return + + except Exception as e: + print(f"Error creating plot: {e}") + return + + +def deep_map_to_anchor_cell(model, data, batch_size=64, device=None, process_info_path=None, channel=None): + """ + Approximate anchor-cell mapping with a trained dCellAligner model. + + This function mirrors the role of ``map_to_anchor_cell``, but instead of + directly computing a GW/FGW mappings to the anchor cell it uses a trained + dCellAligner model to infer the mapped single cell images. Supports + CellAligner_Cell objects, NumPy arrays of images, PyTorch datasets, and + PairedDatasets as inputs. + + :param model: Trained dCellAligner model. + :type model: torch.nn.Module + :param data: Input data to extract embeddings from. Can be: + - PairedDataset + - PyTorch Dataset + - List of CellAligner_Cell objects + - NumPy array with shape (N, H, W, 3) or (H, W, 3) + :type data: PairedDataset, Dataset, numpy.ndarray, list, or tuple + :param batch_size: Number of prepared images to process per forward pass. Default is 64. + :type batch_size: int + :param device: Device to run inference on. If None, uses the model's device. + :type device: torch.device or None + :param process_info_path: Path to the saved cell image processing JSON or the directory containing it. + Used when ``data`` contains CellAligner_Cell objects. + :type process_info_path: str or None, optional + :param channel: Channel name to use when ``data`` contains CellAligner_Cell objects or file paths. + :type channel: str or None, optional + :return: Array of mapped protein images with shape (N, H, W). + :rtype: numpy.ndarray + """ + if device is None: + device = next(model.parameters()).device + + data = _as_sequence_if_object_array(data) + processing_info = _load_cell_image_processing_info(process_info_path) + + if isinstance(data, PairedDataset): + if not hasattr(data, 'image_pairs') or not hasattr(data, 'image_dir'): + raise TypeError('PairedDataset input must define image_pairs and image_dir') + + _, data = _paired_dataset_to_indexed_image_dataset(data) + if isinstance(data, Dataset): + loader = DataLoader(data, batch_size=batch_size, shuffle=False) + mapped_images = [] + with torch.no_grad(): + for batch in tqdm(loader, desc='Mapping cells'): + batch_tensor = _batch_to_tensor(batch, device) + _, reconstructed, _ = model(batch_tensor, batch_tensor) + mapped_images.append(reconstructed[:, 0, :, :].cpu().numpy()) + + return np.concatenate(mapped_images, axis=0) + else: + prepared_images = None + if isinstance(data, np.ndarray) and data.dtype != object: + prepared_images = data + else: + if not isinstance(data, (list, tuple)): + data = [data] + else: + data = _as_sequence_if_object_array(data) + + if data and (_is_cellaligner_cell_like(data[0]) or isinstance(data[0], str)): + if process_info_path is None: + raise ValueError("process_info_path must be provided when data contains CellAligner_Cell objects or file paths.") + prepared_images = [_prepare_cellaligner_image(cell_object, channel=channel, processing_info=processing_info) for cell_object in data] + prepared_images = np.stack(prepared_images, axis=0) + else: + raise TypeError('data must be a NumPy array, Dataset, PairedDataset, or a sequence of CellAligner_Cell objects') + model.eval() + mapped_images = [] + with torch.no_grad(): + for start_i in tqdm(range(0, len(prepared_images), batch_size), desc='Mapping cells'): + batch_images = prepared_images[start_i:start_i + batch_size] + batch_tensor = _batch_to_tensor(batch_images, device) + _, reconstructed, _ = model(batch_tensor, batch_tensor) + mapped_images.append(reconstructed[:, 0, :, :].cpu().numpy()) + + return np.concatenate(mapped_images, axis=0) + + +def generate_dataset_split_pairs(indices, n_pairs, proportions=None, seed=None): + """ + Generate cell pairs for Deep CellAligner dataset splits. + + Creates stratified cell pairs for deep learning model training. Supports both + random sampling across all cells and stratified sampling from predefined groups + to ensure balanced representation across train/validation/test splits. + + :param indices: List of available cell indices corresponding to processed cell images. + :type indices: list or array-like + :param n_pairs: N-length list specifying number of pairs to generate for each dataset split. + Typically [n_train_pairs, n_val_pairs, n_test_pairs]. + :type n_pairs: list of int + :param proportions: N-length list of proportions that sum to 1.0 for stratified sampling. + If provided, cell indices are split into N groups according to these + proportions, and pairs are drawn only within each group. This ensures + train/val/test sets use disjoint cell populations. If None, all pairs + are drawn randomly from all available cells. Default is None. + :type proportions: list of float, optional + :param seed: Random seed for reproducible dataset splits. Default is None. + :type seed: int, optional + :returns: N-length list where each element is a 2D array of shape (n_pairs, 2) + containing cell index pairs for each dataset split (train, val, test). + :rtype: list of numpy.ndarray + """ + if seed is not None: + np.random.seed(seed) + + indices = np.array(indices) + n_groups = len(n_pairs) # Number of dataset splits (train, val, test) + + if proportions is not None: + if len(proportions) != n_groups: + raise ValueError(f"Length of proportions ({len(proportions)}) must match length of n_pairs ({n_groups})") + + if not np.isclose(sum(proportions), 1.0, rtol=1e-5): + raise ValueError(f"Proportions must sum to 1.0, got {sum(proportions)}") + + # Split cell indices into disjoint groups for train/val/test + np.random.shuffle(indices) # Randomize cell order first + n_total = len(indices) + + group_indices = [] + start_idx = 0 + + for i, prop in enumerate(proportions[:-1]): # Handle all but last group + group_size = int(np.round(prop * n_total)) + end_idx = start_idx + group_size + group_indices.append(indices[start_idx:end_idx]) + start_idx = end_idx + + # Last group gets remaining indices + group_indices.append(indices[start_idx:]) + + # Generate pairs within each disjoint cell group (train, val, test) + paired_arrays = [] + for i, group_inds in enumerate(group_indices): + n_pairs_for_group = n_pairs[i] + if len(group_inds) < 2: + raise ValueError(f"Dataset split {i} has only {len(group_inds)} cells, need at least 2 to generate pairs") + + pairs = get_random_pairs(group_inds, n_pairs_for_group) + paired_arrays.append(pairs) + + else: + # Generate all pairs randomly from all cells (overlapping populations) + paired_arrays = [] + for n_pairs_for_group in n_pairs: + pairs = get_random_pairs(indices, n_pairs_for_group) + paired_arrays.append(pairs) + + return paired_arrays \ No newline at end of file