[feature proposal] Composing transformations with __add__ magic method
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 17.9k
- Forks
- 7.3k
- Avg merge
- 1d 15h
- Merged PRs (30d)
- 13
Description
TLDR: transform = Resize((299, 299)) + ToTensor()
Motivation
Consider the common use case of building an overall-common-but-differing-at-one-point transformation pipeline for train and test sets, for instance applying augmentation transforms only to train set:
train_transform = transforms.Compose([
Resize((299, 299), interpolation=1),
RandomCrop(299, padding=4),
RandomHorizontalFlip(),
RandomGrayscale(),
ToTensor(),
Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
])
test_transform = transforms.Compose([
Resize((299, 299), interpolation=1),
ToTensor(),
Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
])
A way of expressing of train_transform builds upon test_transform would be handy. What you could do now is to recursively use transforms.Compose (i.e. Compose([Compose(...resizing...), Compose(...augmentation...], Normalize(...)])), but I don't find this particularly clean (and intuitively, final transforms objects should be sequential objects with only one level of depth).
Proposed behavior
common_transforms = ToTensor() + Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
augmentation = RandomCrop(299, padding=4) + RandomHorizontalFlip() + RandomGrayscale()
train_transforms = Resize((299, 299)) + augmentation + common_transforms
test_transforms = Resize((299, 299)) + common_transforms
Note how this mimics the behavior of Python lists (and other iterables):
>>> [1, 2] + [3, 4]
[1, 2, 3, 4]
Implementation sketch
class Transform(object):
# Right now all transforms directly inherit from object.
# Let Transform be a superclass for all transformations
def __call__(self, pic):
raise NotImplementedError('Each subclass should implement this method')
def __repr__(self):
raise NotImplementedError('Each subclass should implement this method')
def __add__(self, other):
if not isinstance(other, Transform):
raise TypeError('Only transformations can be added')
if isinstance(self, Compose) and isinstance(other, Compose):
return Compose(self.transforms + other.transforms)
if not isinstance(self, Compose) and isinstance(other, Compose):
other.transforms = self + other.transforms
return other
if isinstance(self, Compose) and not isinstance(other, Compose):
self.transforms = self.transforms + other
return self
if not isinstance(self, Compose) and not isinstance(other, Compose):
return Compose([self, other])
Comments are most welcome!
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by reviewing the existing transformation classes and the Compose entry point; the proposal names Transform, Compose, and add as the relevant interfaces. Confirm how single transforms and composed transforms should combine, including invalid operands, and verify that the resulting pipelines preserve the proposed ordering and flat structure.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- computer-vision
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100