Wrappers

Consider the following dataset, which is a simple loader for MNIST:

class MNIST:
    # ...

    def load_image(self, identifier: str):
        return self.xs[int(identifier)]

    def load_label(self, identifier: str):
        return self.ys[int(identifier)]

# The full implementation can be found at `dpipe.tests.mnist.resources`:
# from dpipe.tests.mnist.resources import MNIST

dataset = MNIST('PATH TO DATA')
dataset.load_image(0).shape, dataset.load_label(0)
((1, 28, 28), 5)

Next, suppose you want to upsample the images by a factor of 2.

There are several solutions:

  • Rewrite the dataset - breaks compatibility, not reusable

  • Write a new dataset - not reusable, generates a lot of repetitive code

  • Subclass the dataset - not reusable

  • Wrap the dataset

Wrappers are handy when you need to change the dataset’s behaviour in a reusable way.

You can think of a wrapper as an additional layer around the original dataset. In case of upsampling it could look something like this:

from dpipe.dataset.wrappers import Proxy
from dpipe.medim.shape_ops import zoom

class UpsampleWrapper(Proxy):
    def load_image(self, identifier):
        # self._shadowed is the original dataset
        image = self._shadowed.load_image(identifier)
        image = zoom(image, [2, 2])
        return image
upsampled = UpsampleWrapper(dataset)
upsampled.load_image(0).shape, upsampled.load_label(0)
((1, 56, 56), 5)

Now this wrapper can be reused with other datasets that have the load_image method. Note that load_label is also working, even though it wasn’t defined in the wrapper.

dpipe already has a collection of predefined wrappers, for example, you can apply upsampling as follows:

from dpipe.dataset.wrappers import apply

upsampled = apply(dataset, load_image=lambda image: zoom(image, [2, 2]))

or in a more functional fashion:

from functools import partial

upsampled = apply(dataset, load_image=partial(zoom, scale_factor=[2, 2]))