aboutsummaryrefslogtreecommitdiffziptar.gz
path: root/tests/test_sketch.py
diff options
context:
space:
mode:
authorgodosa <godosa@godosa.eu>2026-10-06 23:52:03 +0200
committergodosa <godosa@godosa.eu>2026-10-06 23:52:03 +0200
commit346b1c5195bffc71ceaa9262453e3c189656400b (patch)
tree01ac0d31e2724cd6abcc689a5a228e2cbea2f6cf /tests/test_sketch.py
downloadworldgen-346b1c5195bffc71ceaa9262453e3c189656400b.tar.gz
worldgen-346b1c5195bffc71ceaa9262453e3c189656400b.zip
worldgen: initial public history
Diffstat (limited to 'tests/test_sketch.py')
-rw-r--r--tests/test_sketch.py151
1 files changed, 151 insertions, 0 deletions
diff --git a/tests/test_sketch.py b/tests/test_sketch.py
new file mode 100644
index 0000000..f827943
--- /dev/null
+++ b/tests/test_sketch.py
@@ -0,0 +1,151 @@
+import shutil
+import tempfile
+import unittest
+from pathlib import Path
+
+import numpy as np
+from PIL import Image
+
+from mapgen import sketch as SK
+from mapgen.pipeline import StageError
+from tests.helpers import make_ctx
+
+
+class SampleTest(unittest.TestCase):
+ def test_bilinear_centres_and_wrap(self):
+ img = np.arange(32, dtype=np.float64).reshape(4, 8)
+ # pixel (1, 2) centre: lon = 2.5/8*360-180 = -67.5, lat = 90-1.5/4*180 = 22.5
+ self.assertAlmostEqual(SK.sample_equirect(img, np.array([22.5]), np.array([-67.5]))[0], img[1, 2])
+ a = SK.sample_equirect(img, np.array([22.5]), np.array([179.999]))[0]
+ b = SK.sample_equirect(img, np.array([22.5]), np.array([-179.999]))[0]
+ self.assertAlmostEqual(a, b, places=2) # wraps across the antimeridian
+ self.assertAlmostEqual(SK.sample_equirect(img, np.array([90.0]), np.array([-157.5]))[0], img[0, 0])
+
+ def test_mask_8bit_and_any_size(self):
+ tmp = Path(tempfile.mkdtemp())
+ try:
+ Image.fromarray(np.full((19, 37), 255, np.uint8), "L").save(tmp / "a.png")
+ Image.fromarray(np.full((10, 20), 32768, np.uint16)).save(tmp / "b.png")
+ self.assertGreater(SK.load_mask(tmp / "a.png").min(), 0.99)
+ self.assertAlmostEqual(float(np.abs(SK.load_mask(tmp / "b.png")).max()), 0.0)
+ finally:
+ shutil.rmtree(tmp)
+
+
+class StageTest(unittest.TestCase):
+ def setUp(self):
+ self.tmp = Path(tempfile.mkdtemp())
+
+ def tearDown(self):
+ shutil.rmtree(self.tmp)
+
+ def test_requires_import(self):
+ ctx = make_ctx(1, root=self.tmp)
+ with self.assertRaisesRegex(StageError, "new-world"):
+ SK.run(ctx)
+
+ def test_outputs(self):
+ (self.tmp / "sketch").mkdir()
+ land = np.zeros((100, 200), np.uint8)
+ land[:, :100] = 255 # western hemisphere is land
+ for n in SK.SKETCH:
+ Image.fromarray(land if n == "land" else np.zeros_like(land), "L").save(self.tmp / "sketch" / f"{n}.png")
+ ctx = make_ctx(2, root=self.tmp)
+ out = SK.run(ctx)
+ g = ctx.grid
+ self.assertGreater(out["sk_land"][g.lon < -10].mean(), 0.95)
+ self.assertLess(out["sk_land"][g.lon > 10].mean(), 0.05)
+ for m in SK.MASKS:
+ self.assertTrue(np.all(out[f"m_{m}"] == 0))
+
+
+class MaskFormatsTest(unittest.TestCase):
+ def setUp(self):
+ self.tmp = Path(tempfile.mkdtemp())
+
+ def tearDown(self):
+ shutil.rmtree(self.tmp)
+
+ def test_gimp_formats(self):
+ Image.fromarray(np.full((8, 16), 128, np.uint8), "L").save(self.tmp / "grey.png")
+ pal = Image.new("P", (16, 8), 0)
+ pal.putpalette([128, 128, 128, 255, 255, 255] + [0] * 762)
+ pal.save(self.tmp / "pal_grey.png")
+ pal.paste(1, (0, 0, 16, 8))
+ pal.save(self.tmp / "pal_white.png")
+ Image.new("1", (16, 8), 1).save(self.tmp / "bit_white.png")
+ Image.new("RGBA", (16, 8), (0, 0, 0, 0)).save(self.tmp / "clear.png")
+ self.assertEqual(float(np.abs(SK.load_mask(self.tmp / "grey.png")).max()), 0.0)
+ self.assertEqual(float(np.abs(SK.load_mask(self.tmp / "pal_grey.png")).max()), 0.0)
+ self.assertAlmostEqual(float(SK.load_mask(self.tmp / "pal_white.png").min()), 1.0)
+ self.assertAlmostEqual(float(SK.load_mask(self.tmp / "bit_white.png").min()), 1.0)
+ self.assertEqual(float(np.abs(SK.load_mask(self.tmp / "clear.png")).max()), 0.0)
+
+ def test_corrupt_mask_is_stage_error(self):
+ (self.tmp / "bad.png").write_bytes(b"not a png")
+ with self.assertRaisesRegex(StageError, "bad.png"):
+ SK.load_mask(self.tmp / "bad.png")
+
+
+class SketchWarpTest(unittest.TestCase):
+ def setUp(self):
+ self.tmp = Path(tempfile.mkdtemp())
+ (self.tmp / "sketch").mkdir()
+ lat = 90 - (np.arange(100) + 0.5) * 1.8
+ lon = (np.arange(200) + 0.5) * 1.8 - 180
+ LA, LO = np.meshgrid(lat, lon, indexing="ij")
+ land = ((np.abs(LA) < 35) & (np.abs(LO) < 60)).astype(np.uint8) * 255
+ for n in SK.SKETCH:
+ Image.fromarray(land if n == "land" else np.zeros_like(land), "L").save(self.tmp / "sketch" / f"{n}.png")
+
+ def tearDown(self):
+ shutil.rmtree(self.tmp)
+
+ def _land(self, **over):
+ ctx = make_ctx(3, root=self.tmp, cfg={"sketch": over} if over else None)
+ return SK.run(ctx)["sk_land"] > 0.5, ctx.grid
+
+ def test_warp_off_matches_drawing(self):
+ warped, g = self._land(warp_km=0.0, detail_warp_km=0.0)
+ direct = SK.sample_equirect(SK.load_png01(self.tmp / "sketch" / "land.png"), g.lat, g.lon) > 0.5
+ np.testing.assert_array_equal(warped, direct)
+
+ def test_default_warp_deviates_but_keeps_continent(self):
+ straight, g = self._land(warp_km=0.0, detail_warp_km=0.0)
+ warped, _ = self._land()
+ iou = (warped & straight).sum() / (warped | straight).sum()
+ self.assertTrue(0.35 < iou < 0.85, iou)
+ again, _ = self._land()
+ np.testing.assert_array_equal(warped, again)
+
+
+class SketchMoveTest(unittest.TestCase):
+ def setUp(self):
+ self.tmp = Path(tempfile.mkdtemp())
+ (self.tmp / "sketch").mkdir()
+ lat = 90 - (np.arange(200) + 0.5) * 0.9
+ lon = (np.arange(400) + 0.5) * 0.9 - 180
+ LA, LO = np.meshgrid(lat, lon, indexing="ij")
+ land = (((LA - 0) ** 2 + (LO - 0) ** 2 < 15 ** 2) | ((LA + 40) ** 2 + (LO - 100) ** 2 < 10 ** 2))
+ for n in SK.SKETCH:
+ Image.fromarray((land * 255).astype(np.uint8) if n == "land" else np.zeros(land.shape, np.uint8), "L") \
+ .save(self.tmp / "sketch" / f"{n}.png")
+
+ def tearDown(self):
+ shutil.rmtree(self.tmp)
+
+ def _land(self, moves):
+ ctx = make_ctx(4, root=self.tmp, cfg={"sketch": {"warp_km": 0.0, "detail_warp_km": 0.0, "moves": moves}})
+ return SK.run(ctx)["sk_land"] > 0.5, ctx.grid
+
+ def test_move_rotates_one_continent_and_keeps_its_area(self):
+ before, g = self._land([])
+ after, _ = self._land([{"at": [0.0, 0.0], "to": [30.0, 0.0]}])
+ near = lambda la, lo, r: g.radius_km * np.arccos(np.clip(g.xyz @ g.xyz[g.cell_index(la, lo)], -1, 1)) < r
+ self.assertTrue(before[near(0.0, 0.0, 800)].all() and not after[near(0.0, 0.0, 800)].any())
+ self.assertTrue(after[near(30.0, 0.0, 800)].all())
+ other = near(-40.0, 100.0, 600)
+ np.testing.assert_array_equal(before[other], after[other]) # the other island stays
+ a = g.area_km2
+ moved_area = a[after & ~other].sum() / a[before & ~other].sum()
+ self.assertAlmostEqual(moved_area, 1.0, delta=0.05) # rotation preserves area