From 3ef7175e425a4d50f97487676b05103292c3838e Mon Sep 17 00:00:00 2001 From: Jayneel Shah <80264736+jayneel-shah18@users.noreply.github.com> Date: Tue, 18 Mar 2025 02:19:21 +0000 Subject: [PATCH 1/4] add test file --- imagery/i.smap/testsuite/test_i_smap.py | 155 ++++++++++++++++++++++++ 1 file changed, 155 insertions(+) create mode 100644 imagery/i.smap/testsuite/test_i_smap.py diff --git a/imagery/i.smap/testsuite/test_i_smap.py b/imagery/i.smap/testsuite/test_i_smap.py new file mode 100644 index 00000000000..e6223930c76 --- /dev/null +++ b/imagery/i.smap/testsuite/test_i_smap.py @@ -0,0 +1,155 @@ +import grass.script as gs +from grass.gunittest.case import TestCase +from grass.gunittest.main import test + + +class TestISmapWithSyntheticData(TestCase): + group_name = "test_smap_group" + subgroup_name = "test_smap_subgroup" + input_maps = ["synth_map1", "synth_map2", "synth_map3"] + training_map = "training_areas" + signature_file = "smap_sig" + output_map = "smap_output" + goodness_map = "smap_goodness" + temp_rasters = [] + + @classmethod + def setUpClass(cls): + """Set up test environment""" + cls.use_temp_region() + cls.runModule("g.region", n=50, s=0, e=50, w=0, rows=100, cols=100) + cls.runModule( + "r.mapcalc", + expression=f"{cls.input_maps[0]} = 10 * sin(row() / 10.0) + 10 * sin(col() / 10.0) + 10", + overwrite=True, + ) + cls.runModule( + "r.mapcalc", + expression=f"{cls.input_maps[1]} = 10 * cos(row() / 10.0) + 10 * cos(col() / 10.0) + 10", + overwrite=True, + ) + cls.runModule( + "r.mapcalc", + expression=f"{cls.input_maps[2]} = 20 * exp(-((row() - 50)^2 + (col() - 50)^2) / 500)", + overwrite=True, + ) + cls.temp_rasters.extend(cls.input_maps) + cls.runModule( + "r.mapcalc", + expression=f"{cls.training_map} = if(row() < 40 && col() < 40, 1, if(row() > 60 && col() > 60, 3, 2))", + overwrite=True, + ) + cls.temp_rasters.append(cls.training_map) + cls.runModule( + "r.colors", map=cls.training_map, rules="-", stdin="1 red\n2 green\n3 blue" + ) + cls.runModule( + "i.group", + group=cls.group_name, + subgroup=cls.subgroup_name, + input=",".join(cls.input_maps), + ) + cls.runModule( + "i.gensigset", + trainingmap=cls.training_map, + group=cls.group_name, + subgroup=cls.subgroup_name, + signaturefile=cls.signature_file, + overwrite=True, + ) + + @classmethod + def tearDownClass(cls): + """Cleanup test environment""" + cls.runModule("g.remove", flags="f", type="group", name=cls.group_name) + cls.temp_rasters.append(cls.output_map) + cls.runModule("g.remove", flags="f", type="raster", name=cls.temp_rasters) + cls.runModule("g.remove", flags="f", type="raster", name=cls.signature_file) + cls.del_temp_region() + + def _run_smap(self, output_name, **kwargs): + self.assertModule( + "i.smap", + group=self.group_name, + subgroup=self.subgroup_name, + signaturefile=self.signature_file, + output=output_name, + overwrite=True, + **kwargs, + ) + self.assertRasterExists(output_name) + return output_name + + def test_basic_classification(self): + """Test basic SMAP classification with default parameters""" + self._run_smap(f"{self.output_map}_basic") + self.assertRasterExists(f"{self.output_map}_basic") + self.temp_rasters.append(f"{self.output_map}_basic") + + stats = gs.read_command("r.stats", flags="cn", input=f"{self.output_map}_basic") + unique_classes = len( + [line for line in stats.split("\n") if line.strip() and " " in line] + ) + self.assertEqual( + unique_classes, 3, f"Expected 3 classes in output, found {unique_classes}" + ) + + def test_with_goodness_map(self): + """Test that goodness of fit map is properly generated""" + self._run_smap(f"{self.output_map}_goodness", goodness=self.goodness_map) + self.assertRasterExists(self.goodness_map) + self.temp_rasters.extend([self.goodness_map, f"{self.output_map}_goodness"]) + + reference_stats = { + "min": -7.328390, + "max": 3.600804, + } + self.assertRasterFitsUnivar(self.goodness_map, reference_stats, precision=1e-6) + + def test_maximum_likelihood_flag(self): + """Test the -m flag for maximum likelihood estimation""" + self._run_smap(f"{self.output_map}_smap") + self.assertRasterExists(f"{self.output_map}_smap") + self.temp_rasters.append(f"{self.output_map}_smap") + + self._run_smap(f"{self.output_map}_mle", flags="m") + self.assertRasterExists(f"{self.output_map}_mle") + self.temp_rasters.append(f"{self.output_map}_mle") + + kappa_result = gs.parse_command( + "r.kappa", + classification=f"{self.output_map}_smap", + reference=f"{self.output_map}_mle", + format="json", + ) + + overall_accuracy = float(kappa_result["overall_accuracy"]) + + self.assertGreater( + overall_accuracy, 95.0, "Overall accuracy should be reasonably high" + ) + self.assertLess( + overall_accuracy, 100.0, "SMAP and ML should not have 100% agreement" + ) + + def test_block_size(self): + """Test that block size does not affect output""" + + baseline = self._run_smap(f"{self.output_map}_baseline", flags="m") + bs1 = self._run_smap(f"{self.output_map}_bs1", flags="m", blocksize=256) + bs2 = self._run_smap(f"{self.output_map}_bs2", flags="m", blocksize=1024) + + self.temp_rasters.extend( + [ + f"{self.output_map}_baseline", + f"{self.output_map}_bs1", + f"{self.output_map}_bs2", + ] + ) + + self.assertRastersEqual(baseline, bs1) + self.assertRastersEqual(baseline, bs2) + + +if __name__ == "__main__": + test() From 77c63735904345a2d0ae16013719fe2a2ee13136 Mon Sep 17 00:00:00 2001 From: Jayneel Shah <80264736+jayneel-shah18@users.noreply.github.com> Date: Tue, 18 Mar 2025 03:55:49 +0000 Subject: [PATCH 2/4] made minor fixes --- imagery/i.smap/testsuite/test_i_smap.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/imagery/i.smap/testsuite/test_i_smap.py b/imagery/i.smap/testsuite/test_i_smap.py index e6223930c76..38fa175a91e 100644 --- a/imagery/i.smap/testsuite/test_i_smap.py +++ b/imagery/i.smap/testsuite/test_i_smap.py @@ -68,6 +68,7 @@ def tearDownClass(cls): cls.del_temp_region() def _run_smap(self, output_name, **kwargs): + """Helper function to run i.smap""" self.assertModule( "i.smap", group=self.group_name, @@ -102,7 +103,7 @@ def test_with_goodness_map(self): reference_stats = { "min": -7.328390, - "max": 3.600804, + "max": 4.495414, } self.assertRasterFitsUnivar(self.goodness_map, reference_stats, precision=1e-6) @@ -134,7 +135,6 @@ def test_maximum_likelihood_flag(self): def test_block_size(self): """Test that block size does not affect output""" - baseline = self._run_smap(f"{self.output_map}_baseline", flags="m") bs1 = self._run_smap(f"{self.output_map}_bs1", flags="m", blocksize=256) bs2 = self._run_smap(f"{self.output_map}_bs2", flags="m", blocksize=1024) From 9968f5a76ba7ea9194dc9797a6c9e4b6c2b04b98 Mon Sep 17 00:00:00 2001 From: Jayneel Shah <80264736+jayneel-shah18@users.noreply.github.com> Date: Tue, 18 Mar 2025 16:38:13 +0000 Subject: [PATCH 3/4] fix the docstrings --- imagery/i.smap/testsuite/test_i_smap.py | 27 +++++++++++++++---------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/imagery/i.smap/testsuite/test_i_smap.py b/imagery/i.smap/testsuite/test_i_smap.py index 38fa175a91e..4d99872d280 100644 --- a/imagery/i.smap/testsuite/test_i_smap.py +++ b/imagery/i.smap/testsuite/test_i_smap.py @@ -3,7 +3,9 @@ from grass.gunittest.main import test -class TestISmapWithSyntheticData(TestCase): +class TestISmap(TestCase): + """Regression tests for i.smap GRASS GIS module.""" + group_name = "test_smap_group" subgroup_name = "test_smap_subgroup" input_maps = ["synth_map1", "synth_map2", "synth_map3"] @@ -15,7 +17,7 @@ class TestISmapWithSyntheticData(TestCase): @classmethod def setUpClass(cls): - """Set up test environment""" + """Set up the input data and configure test environment.""" cls.use_temp_region() cls.runModule("g.region", n=50, s=0, e=50, w=0, rows=100, cols=100) cls.runModule( @@ -60,7 +62,7 @@ def setUpClass(cls): @classmethod def tearDownClass(cls): - """Cleanup test environment""" + """Clean up generated data and reset the region.""" cls.runModule("g.remove", flags="f", type="group", name=cls.group_name) cls.temp_rasters.append(cls.output_map) cls.runModule("g.remove", flags="f", type="raster", name=cls.temp_rasters) @@ -68,7 +70,7 @@ def tearDownClass(cls): cls.del_temp_region() def _run_smap(self, output_name, **kwargs): - """Helper function to run i.smap""" + """Helper function to execute i.smap with common parameters.""" self.assertModule( "i.smap", group=self.group_name, @@ -82,7 +84,7 @@ def _run_smap(self, output_name, **kwargs): return output_name def test_basic_classification(self): - """Test basic SMAP classification with default parameters""" + """Verify basic SMAP classification produces valid results.""" self._run_smap(f"{self.output_map}_basic") self.assertRasterExists(f"{self.output_map}_basic") self.temp_rasters.append(f"{self.output_map}_basic") @@ -96,7 +98,10 @@ def test_basic_classification(self): ) def test_with_goodness_map(self): - """Test that goodness of fit map is properly generated""" + """ + Validate goodness of fit map generation and + verify if map values fall within expected statistical range + """ self._run_smap(f"{self.output_map}_goodness", goodness=self.goodness_map) self.assertRasterExists(self.goodness_map) self.temp_rasters.extend([self.goodness_map, f"{self.output_map}_goodness"]) @@ -108,7 +113,7 @@ def test_with_goodness_map(self): self.assertRasterFitsUnivar(self.goodness_map, reference_stats, precision=1e-6) def test_maximum_likelihood_flag(self): - """Test the -m flag for maximum likelihood estimation""" + """Compare SMAP and Maximum Likelihood Estimation (-m flag) approaches""" self._run_smap(f"{self.output_map}_smap") self.assertRasterExists(f"{self.output_map}_smap") self.temp_rasters.append(f"{self.output_map}_smap") @@ -134,10 +139,10 @@ def test_maximum_likelihood_flag(self): ) def test_block_size(self): - """Test that block size does not affect output""" - baseline = self._run_smap(f"{self.output_map}_baseline", flags="m") - bs1 = self._run_smap(f"{self.output_map}_bs1", flags="m", blocksize=256) - bs2 = self._run_smap(f"{self.output_map}_bs2", flags="m", blocksize=1024) + """Ensure block size parameter doesn't affect results""" + baseline = self._run_smap(f"{self.output_map}_baseline") + bs1 = self._run_smap(f"{self.output_map}_bs1", blocksize=256) + bs2 = self._run_smap(f"{self.output_map}_bs2", blocksize=1024) self.temp_rasters.extend( [ From b2fe75bcb5a82cd8fa0086c9901563f23bff697d Mon Sep 17 00:00:00 2001 From: Jayneel Shah <80264736+jayneel-shah18@users.noreply.github.com> Date: Tue, 25 Mar 2025 23:24:13 +0000 Subject: [PATCH 4/4] fixed the removal of signature file using proper command --- imagery/i.smap/testsuite/test_i_smap.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/imagery/i.smap/testsuite/test_i_smap.py b/imagery/i.smap/testsuite/test_i_smap.py index 4d99872d280..07b5dd19864 100644 --- a/imagery/i.smap/testsuite/test_i_smap.py +++ b/imagery/i.smap/testsuite/test_i_smap.py @@ -66,7 +66,7 @@ def tearDownClass(cls): cls.runModule("g.remove", flags="f", type="group", name=cls.group_name) cls.temp_rasters.append(cls.output_map) cls.runModule("g.remove", flags="f", type="raster", name=cls.temp_rasters) - cls.runModule("g.remove", flags="f", type="raster", name=cls.signature_file) + cls.runModule("i.signatures", remove=cls.signature_file, type="sigset") cls.del_temp_region() def _run_smap(self, output_name, **kwargs):