"""Offline tests for Align Image and Image Size.""" from __future__ import annotations import sys import unittest from pathlib import Path import torch ROOT = Path(__file__).resolve().parent.parent sys.path.insert(0, str(ROOT)) if "nodes" in sys.modules and not hasattr(sys.modules["nodes"], "__path__"): del sys.modules["nodes"] from nodes._align_helpers import ALIGN_MODES, align_image, aligned_size from nodes.node_align_image import AusBossAlignImage from nodes.node_image_size import AusBossImageSize class AlignedSizeTests(unittest.TestCase): def test_crop_rounds_down_pad_rounds_up_resize_rounds_nearest(self): # 1000 / 16 = 62.5: down 992, up 1008, nearest ties-up 1008. self.assertEqual(aligned_size(1000, 1000, 16, "crop"), (992, 992)) self.assertEqual(aligned_size(1000, 1000, 16, "pad"), (1008, 1008)) self.assertEqual(aligned_size(1000, 1000, 16, "resize"), (1008, 1008)) # 1030 is nearer 1024 than 1040. self.assertEqual(aligned_size(1030, 500, 16, "resize"), (1024, 496)) def test_already_aligned_sizes_pass_through_every_mode(self): for mode in ALIGN_MODES: self.assertEqual(aligned_size(1024, 768, 32, mode), (1024, 768)) def test_a_side_smaller_than_one_multiple_snaps_up_to_one(self): # Nothing can crop below the smallest legal size. for mode in ALIGN_MODES: self.assertEqual(aligned_size(20, 100, 32, mode)[0], 32, mode) def test_multiple_one_changes_nothing(self): for mode in ALIGN_MODES: self.assertEqual(aligned_size(1013, 777, 1, mode), (1013, 777)) def test_unknown_mode_raises(self): with self.assertRaises(ValueError): aligned_size(100, 100, 16, "stretch") def ramp(batch: int, height: int, width: int) -> torch.Tensor: """A BHWC batch whose pixel values encode their own x coordinate.""" xs = torch.arange(width, dtype=torch.float32) / max(1, width - 1) return xs.view(1, 1, -1, 1).expand(batch, height, width, 3).clone() class AlignImageTests(unittest.TestCase): def test_resize_hits_the_nearest_multiple(self): out, width, height, ox, oy = align_image(ramp(2, 500, 1000), 16, "resize") self.assertEqual((width, height), (1008, 496)) self.assertEqual(tuple(out.shape), (2, 496, 1008, 3)) self.assertEqual((ox, oy), (0, 0)) def test_crop_is_centered_and_lossless_in_the_kept_region(self): frames = ramp(1, 100, 100) out, width, height, ox, oy = align_image(frames, 32, "crop") self.assertEqual((width, height), (96, 96)) # Center crop of 100 -> 96 drops 2 columns each side. self.assertTrue(torch.equal(out, frames[:, 2:98, 2:98, :])) # The original's corner now sits 2 px outside the output. self.assertEqual((ox, oy), (-2, -2)) def test_pad_replicates_edges_up_to_the_next_multiple(self): frames = ramp(1, 100, 100) out, width, height, ox, oy = align_image(frames, 32, "pad") self.assertEqual((width, height), (128, 128)) # 28 extra pixels split 14/14; the original sits centered, untouched. self.assertTrue(torch.equal(out[:, 14:114, 14:114, :], frames)) # The offsets point exactly at where the original landed. self.assertEqual((ox, oy), (14, 14)) self.assertTrue( torch.equal(out[:, oy : oy + 100, ox : ox + 100, :], frames) ) # Padding replicates the border, so corners equal the corner pixel. self.assertTrue(torch.equal(out[0, 0, 0], frames[0, 0, 0])) self.assertTrue(torch.equal(out[0, -1, -1], frames[0, -1, -1])) def test_already_aligned_input_is_returned_as_the_same_object(self): frames = ramp(1, 64, 128) out, width, height, _ox, _oy = align_image(frames, 32, "crop") self.assertIs(out, frames) self.assertEqual((width, height), (128, 64)) def test_crop_mode_pads_a_side_it_cannot_crop(self): # 20 px wide with multiple 32: crop cannot reach a multiple below, # so that side replicate-pads up to exactly one multiple. out, width, height, ox, oy = align_image(ramp(1, 100, 20), 32, "crop") self.assertEqual((width, height), (32, 96)) self.assertEqual(tuple(out.shape), (1, 96, 32, 3)) # Mixed axes: the padded side is positive, the cropped side negative. self.assertEqual((ox, oy), (6, -2)) def test_color_pad_keeps_an_alpha_channel_opaque(self): frames = torch.cat([ramp(1, 20, 20), torch.full((1, 20, 20, 1), 0.25)], dim=-1) out, width, height, ox, oy = align_image( frames, 16, "pad", pad_fill="color", pad_color="#ff0000" ) self.assertEqual((width, height), (32, 32)) self.assertEqual(tuple(out.shape), (1, 32, 32, 4)) self.assertTrue(torch.equal(out[:, oy : oy + 20, ox : ox + 20, :], frames)) # The padded border takes the color, and the alpha channel pads opaque. self.assertTrue(torch.equal(out[0, 0, 0], torch.tensor([1.0, 0.0, 0.0, 1.0]))) self.assertTrue(torch.equal(out[0, -1, -1], torch.tensor([1.0, 0.0, 0.0, 1.0]))) def test_batch_and_values_survive(self): frames = ramp(3, 40, 40) out, _w, _h, _ox, _oy = align_image(frames, 16, "pad") self.assertEqual(out.shape[0], 3) self.assertGreaterEqual(float(out.min()), 0.0) self.assertLessEqual(float(out.max()), 1.0) def test_rejects_non_bhwc_input(self): with self.assertRaises(ValueError): align_image(torch.zeros((64, 64, 3)), 16, "resize") class AlignImageNodeTests(unittest.TestCase): def test_node_returns_image_and_size_only(self): # The helper still reports offsets; the node's face stops at the # three outputs a graph wires (offsets were dropped 2026-09-03). node = AusBossAlignImage() result = node.align(ramp(1, 100, 100), 32, "pad") self.assertEqual(len(result), 3) out, width, height = result self.assertEqual((width, height), (128, 128)) self.assertEqual(tuple(out.shape), (1, 128, 128, 3)) for value in (width, height): self.assertIsInstance(value, int) self.assertEqual(AusBossAlignImage.RETURN_NAMES, ("image", "width", "height")) def test_mode_choices_match_the_helper(self): widget = AusBossAlignImage.INPUT_TYPES()["required"]["mode"][0] self.assertEqual(tuple(widget), ALIGN_MODES) class ImageSizeNodeTests(unittest.TestCase): def test_reports_width_height_and_both_edges(self): node = AusBossImageSize() self.assertEqual(node.measure(torch.zeros((2, 480, 832, 3)))["result"], (832, 480, 832, 480, 2)) self.assertEqual(node.measure(torch.zeros((1, 1080, 608, 3)))["result"], (608, 1080, 1080, 608, 1)) def test_square_images_agree_on_both_edges(self): node = AusBossImageSize() self.assertEqual(node.measure(torch.zeros((1, 512, 512, 3)))["result"], (512, 512, 512, 512, 1)) def test_the_node_face_gets_the_same_numbers_as_the_outputs(self): measured = AusBossImageSize().measure(torch.zeros((2, 480, 832, 3))) self.assertEqual(measured["ui"], {"ausboss_size": [832, 480, 832, 480, 2]}) self.assertEqual(list(measured["result"]), measured["ui"]["ausboss_size"]) self.assertEqual(len(measured["result"]), len(AusBossImageSize.RETURN_TYPES)) def test_rejects_non_bhwc_input(self): with self.assertRaises(ValueError): AusBossImageSize().measure(torch.zeros((512, 512))) if __name__ == "__main__": unittest.main()