Yes you right, you should not return a dictionary in ToTensor or any of Transforms class.
Sorry if I answered late (time zone differences!).
But I have a suggestion here. It is better to build your classes modular so you can use them in other tasks with different datasets easily. For instance, maybe you need 3 or 4 images to be transformed or using different transforms on them. In this case you have to edit your ToTensor or Rescale class. So I think it is better to implement all transform classes for only a sample of input, actually, this is the approach has been chosen in PyTorch.
If I want to explain scenario, I can say if want to do other transforms for example adding gaussian noise to your image not landmarks, you will be stuck again and you have change your ToTensor code because still you are returning dictionary or even you are using another transform inside another one. But if your classes only take one tensor as input and return the changed tensor, you can use all of your custom classes in any order or in any dataset you want.

  
    
    
    Custom Dataset with some preprocessing vision
  
  
    When should I do this based on  Dataset  class? Should I do my processing step in  __getitem__() ? If yes, would it be parallel and fast? 

Yes. the __getitem__ calls are the ones run in parallel. Here’s some example: 
class CelebaDataset(Dataset):
    """Custom Dataset for loading CelebA face images"""

    def __init__(self, txt_path, img_dir, transform=None):
    
        df = pd.read_csv(txt_path, sep=" ", index_col=0)
        self.img_dir = img_dir
        self.txt_path = txt_path
        …
  


By the way, I use same approach as pytorch so I really did not think about your ToTensor custom implementation.

usage in preprocessing step


  
      github.com
  
  
    Nikronic/CoarseNet/blob/master/utils/preprocess.py#L98-L101 3

if self.transform is not None:
    X = self.transform(X)
    random.seed(seed)
    y_descreen = self.transform_gt(y_descreen)



  
  
    
    
  
  



usage in DataLoaders


  
      github.com
  
  
    Nikronic/CoarseNet/blob/master/Train.py#L147-L153 7

custom_transforms = Compose([
    RandomResizedCrop(size=224, scale=(0.8, 1.2)),
    RandomRotation(degrees=(-30, 30)),
    RandomHorizontalFlip(p=0.5),
    ToTensor(),
    Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)),
    RandomNoise(p=0.5, mean=0, std=0.1)])



  
  
    
    
  
  



Custom Transform


  
      github.com
  
  
    Nikronic/CoarseNet/blob/master/utils/preprocess.py#L109-L119 2

class RandomNoise(object):
    def __init__(self, p, mean=0, std=0.1):
        self.p = p
        self.mean = mean
        self.std = std


    def __call__(self, img):
        if random.random() <= self.p:
            noise = torch.empty(*img.size(), dtype=torch.float, requires_grad=False)
            return img+noise.normal_(self.mean, self.std)
        return img



  
  
    
    
  
  


Good luck