diff --git a/copienator/commands/annotating_by_label.py b/copienator/commands/annotating_by_label.py index 3e252f0..45e769a 100644 --- a/copienator/commands/annotating_by_label.py +++ b/copienator/commands/annotating_by_label.py @@ -275,15 +275,7 @@ def _existing_group_state( def split_batches(rendered): batches = [] - current = [] - current_height = 0 - - def flush() -> None: - nonlocal current, current_height - if current: - batches.append(current) - current = [] - current_height = 0 + heights = [] index = 0 while index < len(rendered): @@ -302,15 +294,17 @@ def split_batches(rendered): f"maximum group height is {MAX_HEIGHT_PX} px." ) - # Consider the student's complete block before adding its first image. - # Otherwise later images from that same student could overflow the batch. - if current and current_height + student_height > MAX_HEIGHT_PX: - flush() - current.extend(student_items) - current_height += student_height + # Reuse space in earlier groups, keeping each student's block intact. + for batch_index, height in enumerate(heights): + if height + student_height <= MAX_HEIGHT_PX: + batches[batch_index].extend(student_items) + heights[batch_index] += student_height + break + else: + batches.append(list(student_items)) + heights.append(student_height) index = end - flush() return batches diff --git a/tests/test_gui_core.py b/tests/test_gui_core.py index 5e4cdd5..af71f73 100644 --- a/tests/test_gui_core.py +++ b/tests/test_gui_core.py @@ -2236,6 +2236,23 @@ class StandardCliTests(unittest.TestCase): self.assertIn("Copie01", str(raised.exception)) self.assertIn("cannot be split", str(raised.exception)) + def test_grouped_batching_reuses_space_in_earlier_groups(self) -> None: + module = self.modules["annotating_by_label"] + rendered = [ + ("01", "Ex 1", Image.new("RGB", (10, 70)), 0, []), + ("02", "Ex 1", Image.new("RGB", (10, 60)), 0, []), + ("03", "Ex 1", Image.new("RGB", (10, 40)), 0, []), + ("04", "Ex 1", Image.new("RGB", (10, 15)), 0, []), + ("04", "Ex 2", Image.new("RGB", (10, 15)), 0, []), + ] + with patch.object(module, "MAX_HEIGHT_PX", 100): + batches = module.split_batches(rendered) + self.assertEqual([[item[0] for item in batch] for batch in batches], + [["01", "04", "04"], ["02", "03"]]) + self.assertEqual([sum(item[2].height for item in batch) for batch in batches], + [100, 100]) + self.assertEqual(batches[0][-2:], rendered[-2:]) + def test_grouped_annotation_height_limit_is_32000_pixels(self) -> None: module = self.modules["annotating_by_label"] self.assertEqual(module.MAX_HEIGHT_PX, 32000)