{ "cells": [ { "cell_type": "markdown", "id": "b8fc3196-14ba-4a7c-90d0-5beccd03b842", "metadata": {}, "source": [ "# Unit Test for generate_patches_randomly" ] }, { "cell_type": "code", "execution_count": 1, "id": "ed6e88cc-e799-438a-9d4f-e0b5de49b314", "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "/opt/miniconda3/envs/mesa/lib/python3.11/site-packages/geopandas/_compat.py:106: UserWarning: The Shapely GEOS version (3.8.0-CAPI-1.13.1) is incompatible with the GEOS version PyGEOS was compiled with (3.10.4-CAPI-1.16.2). Conversions between both will be slow.\n", " warnings.warn(\n", "OMP: Info #276: omp_set_nested routine deprecated, please use omp_set_max_active_levels instead.\n", "/opt/miniconda3/envs/mesa/lib/python3.11/site-packages/spaghetti/network.py:41: FutureWarning: The next major release of pysal/spaghetti (2.0.0) will drop support for all ``libpysal.cg`` geometries. This change is a first step in refactoring ``spaghetti`` that is expected to result in dramatically reduced runtimes for network instantiation and operations. Users currently requiring network and point pattern input as ``libpysal.cg`` geometries should prepare for this simply by converting to ``shapely`` geometries.\n", " warnings.warn(dep_msg, FutureWarning, stacklevel=1)\n" ] } ], "source": [ "import unittest\n", "import numpy as np\n", "import pandas as pd\n", "import anndata as ad\n", "\n", "import os\n", "os.sys.path.append('../../../')\n", "from mesa.ecospatial import generate_patches_randomly\n", "import mesa.ecospatial._utils as utils" ] }, { "cell_type": "code", "execution_count": 2, "id": "40f5fbd7-c6e9-4795-a800-fc1a9e0a2e25", "metadata": {}, "outputs": [], "source": [ "class TestGeneratePatchesRandomly(unittest.TestCase):\n", " @classmethod\n", " def setUpClass(cls):\n", " # Set up test data with more points\n", " np.random.seed(42) # For reproducibility in data generation\n", " total_points = 5000\n", " cls.spatial_data = pd.DataFrame({\n", " 'x': 1000 * np.random.rand(total_points),\n", " 'y': 1000 * np.random.rand(total_points),\n", " 'library_key': ['sample_1'] * (total_points // 2) + ['sample_2'] * (total_points // 2),\n", " 'cluster_key': np.random.randint(0, 10, size=total_points)\n", " })\n", "\n", " # Create AnnData object from the DataFrame\n", " cls.obs = cls.spatial_data[['library_key']]\n", " cls.obs.index = cls.spatial_data.index.astype(str)\n", " cls.adata = ad.AnnData(X=np.random.rand(len(cls.spatial_data), 50), obs=cls.obs)\n", " cls.adata.obsm['spatial'] = cls.spatial_data[['x', 'y']].values\n", "\n", " # Subset DataFrame for 'sample_1'\n", " cls.df_sample1 = cls.spatial_data[cls.spatial_data['library_key'] == 'sample_1'].copy()\n", " cls.df_sample1.reset_index(drop=True, inplace=True)\n", "\n", " def test_generate_patches_randomly_with_AnnData(self):\n", " # Test with AnnData input\n", " patches = generate_patches_randomly(\n", " spatial_data=self.adata,\n", " library_key='library_key',\n", " library_id='sample_1',\n", " scaling_factor=2,\n", " spatial_key='spatial',\n", " max_overlap=0.5,\n", " random_seed=42\n", " )\n", " expected_patches = 4 \n", " self.assertEqual(len(patches), expected_patches)\n", " self.assertIsInstance(patches, list)\n", "\n", " def test_generate_patches_randomly_with_DataFrame(self):\n", " # Test with DataFrame input\n", " patches = generate_patches_randomly(\n", " spatial_data=self.df_sample1,\n", " library_key='library_key',\n", " library_id='sample_1',\n", " scaling_factor=4,\n", " spatial_key=['x', 'y'],\n", " max_overlap=0.5,\n", " random_seed=42\n", " )\n", " expected_patches = 16 # Since scaling_factor=5, expect 5x5=25 patches\n", " self.assertEqual(len(patches), expected_patches)\n", " self.assertIsInstance(patches, list)\n", "\n", " def test_invalid_spatial_data_type(self):\n", " # Test with invalid spatial_data type\n", " with self.assertRaises(ValueError):\n", " generate_patches_randomly(\n", " spatial_data='invalid_type',\n", " library_key='library_key',\n", " library_id='sample_1',\n", " scaling_factor=2,\n", " spatial_key='spatial',\n", " max_overlap=0.5,\n", " random_seed=42\n", " )\n", "\n", " def test_invalid_library_key(self):\n", " # Test with invalid library_key\n", " with self.assertRaises(KeyError):\n", " generate_patches_randomly(\n", " spatial_data=self.adata,\n", " library_key='invalid_key',\n", " library_id='sample_1',\n", " scaling_factor=2,\n", " spatial_key='spatial',\n", " max_overlap=0.5,\n", " random_seed=42\n", " )\n", "\n", " def test_invalid_library_id(self):\n", " # Test with invalid library_id\n", " with self.assertRaises(ValueError):\n", " generate_patches_randomly(\n", " spatial_data=self.adata,\n", " library_key='library_key',\n", " library_id='invalid_id',\n", " scaling_factor=2,\n", " spatial_key='spatial',\n", " max_overlap=0.5,\n", " random_seed=42\n", " )\n", "\n", " def test_invalid_spatial_key(self):\n", " # Test with invalid spatial_key\n", " with self.assertRaises(KeyError):\n", " generate_patches_randomly(\n", " spatial_data=self.adata,\n", " library_key='library_key',\n", " library_id='sample_1',\n", " scaling_factor=2,\n", " spatial_key='invalid_spatial_key',\n", " max_overlap=0.5,\n", " random_seed=42\n", " )\n", "\n", " def test_zero_scaling_factor(self):\n", " # Test with zero scaling_factor\n", " with self.assertRaises(ValueError):\n", " generate_patches_randomly(\n", " spatial_data=self.adata,\n", " library_key='library_key',\n", " library_id='sample_1',\n", " scaling_factor=0,\n", " spatial_key='spatial',\n", " max_overlap=0.5,\n", " random_seed=42\n", " )\n", "\n", " def test_non_numeric_scaling_factor(self):\n", " # Test with non-numeric scaling_factor\n", " with self.assertRaises(TypeError):\n", " generate_patches_randomly(\n", " spatial_data=self.adata,\n", " library_key='library_key',\n", " library_id='sample_1',\n", " scaling_factor='two',\n", " spatial_key='spatial',\n", " max_overlap=0.5,\n", " random_seed=42\n", " )\n", "\n", " def test_empty_spatial_data_filtered(self):\n", " # Test when spatial_data_filtered is empty\n", " with self.assertRaises(ValueError):\n", " generate_patches_randomly(\n", " spatial_data=self.adata[self.adata.obs['library_key'] == 'non_existent'],\n", " library_key='library_key',\n", " library_id='non_existent',\n", " scaling_factor=2,\n", " spatial_key='spatial',\n", " max_overlap=0.5,\n", " random_seed=42\n", " )\n", "\n", " def test_output_patch_sizes(self):\n", " # Test that patches have correct sizes\n", " patches = generate_patches_randomly(\n", " spatial_data=self.adata,\n", " library_key='library_key',\n", " library_id='sample_1',\n", " scaling_factor=5,\n", " spatial_key='spatial',\n", " max_overlap=0.5,\n", " random_seed=42\n", " )\n", " # Extract min and max spatial values for 'sample_1'\n", " spatial_values = self.adata[self.adata.obs['library_key'] == 'sample_1'].obsm['spatial']\n", " min_coords = spatial_values.min(axis=0)\n", " max_coords = spatial_values.max(axis=0)\n", " expected_patch_width = (max_coords[0] - min_coords[0]) / 5\n", " expected_patch_height = (max_coords[1] - min_coords[1]) / 5\n", " # Check if patches have correct sizes\n", " for patch in patches:\n", " x0, y0, x1, y1 = patch\n", " self.assertAlmostEqual(x1 - x0, expected_patch_width, places=5)\n", " self.assertAlmostEqual(y1 - y0, expected_patch_height, places=5)\n", "\n", " def test_max_overlap(self):\n", " # Test with max_overlap parameter\n", " patches = generate_patches_randomly(\n", " spatial_data=self.df_sample1,\n", " library_key='library_key',\n", " library_id='sample_1',\n", " scaling_factor=4,\n", " spatial_key=['x', 'y'],\n", " max_overlap=0.5,\n", " random_seed=42\n", " )\n", " # Check for overlaps\n", " for i, patch1 in enumerate(patches):\n", " for patch2 in patches[i+1:]:\n", " # Use overlap_check function from mesa.ecospatial\n", " overlap_allowed = utils._overlap_check(\n", " new_patch=patch1,\n", " existing_patches=[patch2],\n", " max_overlap_ratio=0.5\n", " )\n", " self.assertTrue(overlap_allowed)\n", "\n", " def test_min_points(self):\n", " # Test with min_points set too high, expecting zero patches\n", " patches = generate_patches_randomly(\n", " spatial_data=self.df_sample1,\n", " library_key='library_key',\n", " library_id='sample_1',\n", " scaling_factor=4,\n", " spatial_key=['x', 'y'],\n", " min_points=5000, # More than total points in 'sample_1'\n", " max_overlap=0.5,\n", " random_seed=42\n", " )\n", " self.assertEqual(len(patches), 0)\n", "\n", " def test_random_seed_consistency(self):\n", " # Generate patches with the same random_seed\n", " patches1 = generate_patches_randomly(\n", " spatial_data=self.df_sample1,\n", " library_key='library_key',\n", " library_id='sample_1',\n", " scaling_factor=4,\n", " spatial_key=['x', 'y'],\n", " max_overlap=0.5,\n", " random_seed=42\n", " )\n", " patches2 = generate_patches_randomly(\n", " spatial_data=self.df_sample1,\n", " library_key='library_key',\n", " library_id='sample_1',\n", " scaling_factor=4,\n", " spatial_key=['x', 'y'],\n", " max_overlap=0.5,\n", " random_seed=42\n", " )\n", " # Check that the patches are the same\n", " self.assertEqual(patches1, patches2)\n", "\n", " def test_contains_min_points(self):\n", " # Test that each patch contains at least min_points\n", " min_points = 20\n", " patches = generate_patches_randomly(\n", " spatial_data=self.df_sample1,\n", " library_key='library_key',\n", " library_id='sample_1',\n", " scaling_factor=4,\n", " spatial_key=['x', 'y'],\n", " max_overlap=0.5,\n", " random_seed=42,\n", " min_points=min_points\n", " )\n", " spatial_values = self.df_sample1[['x', 'y']].values\n", " for patch in patches:\n", " contains = utils._contains_points(\n", " patch=patch,\n", " spatial_values=spatial_values,\n", " min_points=min_points\n", " )\n", " self.assertTrue(contains)\n", "\n" ] }, { "cell_type": "code", "execution_count": 3, "id": "7c3053ed-ec60-4b26-b774-f1eaad37069f", "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "..............\n", "----------------------------------------------------------------------\n", "Ran 14 tests in 5.393s\n", "\n", "OK\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Warning: Could not generate a new patch within 5 seconds. Returning 0 out of 16 patches\n" ] }, { "data": { "text/plain": [ "" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Run the tests in the notebook\n", "unittest.main(argv=['first-arg-is-ignored'], exit=False)" ] } ], "metadata": { "kernelspec": { "display_name": "Python3.11 (mesa)", "language": "python", "name": "python311_mesa" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.11.3" } }, "nbformat": 4, "nbformat_minor": 5 }