|
1 | 1 | import typing |
2 | 2 |
|
3 | 3 | from torchvision import transforms |
| 4 | +from torchvision.transforms.functional import crop |
4 | 5 |
|
5 | 6 | from invoke_training._shared.data.utils.aspect_ratio_bucket_manager import AspectRatioBucketManager, Resolution |
6 | 7 | from invoke_training._shared.data.utils.resize import resize_to_cover |
@@ -43,32 +44,54 @@ def __call__(self, data: typing.Dict[str, typing.Any]) -> typing.Dict[str, typin |
43 | 44 | for field_name in self.image_field_names: |
44 | 45 | image_fields[field_name] = data[field_name] |
45 | 46 |
|
| 47 | + # Get the first image to determine original size and resolution |
| 48 | + first_image = next(iter(image_fields.values())) |
| 49 | + original_size_hw = (first_image.height, first_image.width) |
| 50 | + |
46 | 51 | for field_name, image in image_fields.items(): |
47 | 52 | # Determine the target image resolution. |
48 | 53 | if self.resolution is not None: |
49 | 54 | resolution = self.resolution |
50 | 55 | resolution_obj = Resolution(resolution, resolution) |
51 | 56 | else: |
52 | | - original_size_hw = (image.height, image.width) |
53 | 57 | resolution_obj = self.aspect_ratio_bucket_manager.get_aspect_ratio_bucket( |
54 | 58 | Resolution.parse(original_size_hw) |
55 | 59 | ) |
56 | 60 |
|
57 | 61 | image = resize_to_cover(image, resolution_obj) |
| 62 | + |
| 63 | + # Apply cropping and record top left crop position |
58 | 64 | if self.center_crop: |
59 | | - image = transforms.CenterCrop(resolution)(image) |
| 65 | + top_left_y = max(0, (image.height - resolution_obj.height) // 2) |
| 66 | + top_left_x = max(0, (image.width - resolution_obj.width) // 2) |
| 67 | + image = transforms.CenterCrop(resolution_obj.to_tuple())(image) |
60 | 68 | else: |
61 | | - image = transforms.RandomCrop(resolution)(image) |
62 | | - |
63 | | - image = transforms.ToTensor()(image) |
| 69 | + crop_transform = transforms.RandomCrop(resolution_obj.to_tuple()) |
| 70 | + top_left_y, top_left_x, h, w = crop_transform.get_params(image, resolution_obj.to_tuple()) |
| 71 | + image = crop(image, top_left_y, top_left_x, resolution_obj.height, resolution_obj.width) |
64 | 72 |
|
| 73 | + # Apply random flip and update top left crop position accordingly |
65 | 74 | if self.random_flip: |
66 | | - image = transforms.RandomHorizontalFlip(p=0.5)(image) |
67 | | - image_fields[field_name] = image |
| 75 | + # TODO: Use a seed for repeatable results |
| 76 | + import random |
| 77 | + |
| 78 | + if random.random() < 0.5: |
| 79 | + top_left_x = original_size_hw[1] - image.width - top_left_x |
| 80 | + image = transforms.RandomHorizontalFlip(p=1.0)(image) |
| 81 | + |
| 82 | + image = transforms.ToTensor()(image) |
68 | 83 |
|
69 | 84 | if field_name in self.fields_to_normalize_to_range_minus_one_to_one: |
70 | 85 | image_fields[field_name] = transforms.Normalize([0.5], [0.5])(image) |
| 86 | + else: |
| 87 | + image_fields[field_name] = image |
71 | 88 |
|
| 89 | + # Store the processed images and metadata |
72 | 90 | for field_name, image in image_fields.items(): |
73 | 91 | data[field_name] = image |
| 92 | + |
| 93 | + # Add metadata fields expected by VAE caching |
| 94 | + data["original_size_hw"] = original_size_hw |
| 95 | + data["crop_top_left_yx"] = (top_left_y, top_left_x) |
| 96 | + |
74 | 97 | return data |
0 commit comments