From 346b1c5195bffc71ceaa9262453e3c189656400b Mon Sep 17 00:00:00 2001 From: godosa Date: Tue, 6 Oct 2026 23:52:03 +0200 Subject: worldgen: initial public history --- tests/test_sketch.py | 151 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 151 insertions(+) create mode 100644 tests/test_sketch.py (limited to 'tests/test_sketch.py') 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 -- cgit