grass_pygrass_grid_test.py 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256
  1. """Test main functions of PyGRASS GridModule"""
  2. import multiprocessing
  3. import pytest
  4. import grass.script as gs
  5. import grass.script.setup as grass_setup
  6. def max_processes():
  7. """Get max useful number of parallel processes to run"""
  8. return min(multiprocessing.cpu_count(), 4)
  9. # GridModule uses C libraries which can easily initialize only once
  10. # and thus can't easily change location/mapset, so we use a subprocess
  11. # to separate individual GridModule calls.
  12. def run_in_subprocess(function):
  13. """Run function in a separate process"""
  14. process = multiprocessing.Process(target=function)
  15. process.start()
  16. process.join()
  17. @pytest.mark.parametrize("processes", list(range(1, max_processes() + 1)) + [None])
  18. def test_processes(tmp_path, processes):
  19. """Check that running with multiple processes works"""
  20. location = "test"
  21. gs.core._create_location_xy(tmp_path, location) # pylint: disable=protected-access
  22. with grass_setup.init(tmp_path / location):
  23. gs.run_command("g.region", s=0, n=50, w=0, e=50, res=1)
  24. surface = "surface"
  25. gs.run_command("r.surf.fractal", output=surface)
  26. def run_grid_module():
  27. # modules/shortcuts calls get_commands which requires GISBASE.
  28. # pylint: disable=import-outside-toplevel
  29. from grass.pygrass.modules.grid import GridModule
  30. grid = GridModule(
  31. "r.slope.aspect",
  32. width=10,
  33. height=5,
  34. overlap=2,
  35. processes=processes,
  36. elevation=surface,
  37. slope="slope",
  38. aspect="aspect",
  39. )
  40. grid.run()
  41. run_in_subprocess(run_grid_module)
  42. info = gs.raster_info("slope")
  43. assert info["min"] > 0
  44. # @pytest.mark.parametrize("split", [False]) # True does not work.
  45. @pytest.mark.parametrize("width", [5, 10, 50]) # None does not work.
  46. @pytest.mark.parametrize("height", [5, 10, 50])
  47. def test_tiling_schemes(tmp_path, width, height):
  48. """Check that different shapes of tiles work"""
  49. location = "test"
  50. gs.core._create_location_xy(tmp_path, location) # pylint: disable=protected-access
  51. with grass_setup.init(tmp_path / location):
  52. gs.run_command("g.region", s=0, n=50, w=0, e=50, res=1)
  53. surface = "surface"
  54. gs.run_command("r.surf.fractal", output=surface)
  55. def run_grid_module():
  56. # modules/shortcuts calls get_commands which requires GISBASE.
  57. # pylint: disable=import-outside-toplevel
  58. from grass.pygrass.modules.grid import GridModule
  59. grid = GridModule(
  60. "r.slope.aspect",
  61. width=width,
  62. height=height,
  63. overlap=2,
  64. processes=max_processes(),
  65. elevation=surface,
  66. slope="slope",
  67. aspect="aspect",
  68. )
  69. grid.run()
  70. run_in_subprocess(run_grid_module)
  71. info = gs.raster_info("slope")
  72. assert info["min"] > 0
  73. @pytest.mark.parametrize("overlap", [0, 1, 2, 5])
  74. def test_overlaps(tmp_path, overlap):
  75. """Check that overlap accepts different values"""
  76. location = "test"
  77. gs.core._create_location_xy(tmp_path, location) # pylint: disable=protected-access
  78. with grass_setup.init(tmp_path / location):
  79. gs.run_command("g.region", s=0, n=50, w=0, e=50, res=1)
  80. surface = "surface"
  81. gs.run_command("r.surf.fractal", output=surface)
  82. def run_grid_module():
  83. # modules/shortcuts calls get_commands which requires GISBASE.
  84. # pylint: disable=import-outside-toplevel
  85. from grass.pygrass.modules.grid import GridModule
  86. grid = GridModule(
  87. "r.slope.aspect",
  88. width=10,
  89. height=5,
  90. overlap=overlap,
  91. processes=max_processes(),
  92. elevation=surface,
  93. slope="slope",
  94. aspect="aspect",
  95. )
  96. grid.run()
  97. run_in_subprocess(run_grid_module)
  98. info = gs.raster_info("slope")
  99. assert info["min"] > 0
  100. @pytest.mark.parametrize("clean", [True, False])
  101. def test_cleans(tmp_path, clean):
  102. """Check that temporary mapsets are cleaned when appropriate"""
  103. location = "test"
  104. mapset_prefix = "abc"
  105. gs.core._create_location_xy(tmp_path, location) # pylint: disable=protected-access
  106. with grass_setup.init(tmp_path / location):
  107. gs.run_command("g.region", s=0, n=50, w=0, e=50, res=1)
  108. surface = "surface"
  109. gs.run_command("r.surf.fractal", output=surface)
  110. def run_grid_module():
  111. # modules/shortcuts calls get_commands which requires GISBASE.
  112. # pylint: disable=import-outside-toplevel
  113. from grass.pygrass.modules.grid import GridModule
  114. grid = GridModule(
  115. "r.slope.aspect",
  116. width=10,
  117. height=5,
  118. overlap=0,
  119. processes=max_processes(),
  120. elevation=surface,
  121. slope="slope",
  122. aspect="aspect",
  123. mapset_prefix=mapset_prefix,
  124. )
  125. grid.run(clean=clean)
  126. run_in_subprocess(run_grid_module)
  127. path = tmp_path / location
  128. prefixed = 0
  129. for item in path.iterdir():
  130. if item.is_dir():
  131. if clean:
  132. # We know right away something is wrong.
  133. assert not item.name.startswith(mapset_prefix), "Mapset not cleaned"
  134. else:
  135. # We need to see if there is at least one prefixed mapset.
  136. prefixed += int(item.name.startswith(mapset_prefix))
  137. if not clean:
  138. assert prefixed, "Not even one prefixed mapset"
  139. @pytest.mark.parametrize("patch_backend", [None, "r.patch", "RasterRow"])
  140. def test_patching_backend(tmp_path, patch_backend):
  141. """Check patching backend works"""
  142. location = "test"
  143. gs.core._create_location_xy(tmp_path, location) # pylint: disable=protected-access
  144. with grass_setup.init(tmp_path / location):
  145. gs.run_command("g.region", s=0, n=50, w=0, e=50, res=1)
  146. points = "points"
  147. reference = "reference"
  148. gs.run_command("v.random", output=points, npoints=100)
  149. gs.run_command(
  150. "v.to.rast", input=points, output=reference, type="point", use="cat"
  151. )
  152. def run_grid_module():
  153. # modules/shortcuts calls get_commands which requires GISBASE.
  154. # pylint: disable=import-outside-toplevel
  155. from grass.pygrass.modules.grid import GridModule
  156. grid = GridModule(
  157. "v.to.rast",
  158. width=10,
  159. height=5,
  160. overlap=0,
  161. patch_backend=patch_backend,
  162. processes=max_processes(),
  163. input=points,
  164. output="output",
  165. type="point",
  166. use="cat",
  167. )
  168. grid.run()
  169. run_in_subprocess(run_grid_module)
  170. mean_ref = float(gs.parse_command("r.univar", map=reference, flags="g")["mean"])
  171. mean = float(gs.parse_command("r.univar", map="output", flags="g")["mean"])
  172. assert abs(mean - mean_ref) < 0.0001
  173. @pytest.mark.parametrize(
  174. "width, height, processes",
  175. [
  176. (None, None, max_processes()),
  177. (10, None, max_processes()),
  178. (None, 5, max_processes()),
  179. ],
  180. )
  181. def test_tiling(tmp_path, width, height, processes):
  182. """Check auto adjusted tile size based on processes"""
  183. location = "test"
  184. gs.core._create_location_xy(tmp_path, location) # pylint: disable=protected-access
  185. with grass_setup.init(tmp_path / location):
  186. gs.run_command("g.region", s=0, n=50, w=0, e=50, res=1)
  187. surface = "surface"
  188. gs.run_command("r.surf.fractal", output=surface)
  189. def run_grid_module():
  190. # modules/shortcuts calls get_commands which requires GISBASE.
  191. # pylint: disable=import-outside-toplevel
  192. from grass.pygrass.modules.grid import GridModule
  193. grid = GridModule(
  194. "r.slope.aspect",
  195. width=width,
  196. height=height,
  197. overlap=2,
  198. processes=processes,
  199. elevation=surface,
  200. slope="slope",
  201. aspect="aspect",
  202. )
  203. grid.run()
  204. run_in_subprocess(run_grid_module)
  205. info = gs.raster_info("slope")
  206. assert info["min"] > 0