Marek Pokorny
Marek Pokorny

Reputation: 153

Clipping transformed images in matplotlib

I'm trying to display images in matplotlib transformed by a 3x3 matrix (using homogeneous coordinates). I have the following:

import numpy as np
import matplotlib as mp
import matplotlib.pyplot as plt
import matplotlib.image as mpimg

class GenericTransform(mp.transforms.Transform):
    input_dims = 2
    output_dims = 2
    is_separable = False

    # expects a 3x3 matrix as a numpy array
    def __init__(self, matrix):
        super().__init__()
        self.matrix = matrix
        self.has_inverse = (np.linalg.det(matrix) != 0)

    def transform(self, values):
        # append 1 to each coordinate in `values`
        hvals = np.insert(values, 2, 1, axis=1)

        # do the transformations
        thvals = np.matmul(self.matrix, hvals.transpose()).transpose()

        # TODO: perspective divison?

        # get rid of extra dimension and return
        return thvals[:, 0:2]

    def transform_non_affine(self, values):
        return self.transform(values)

    def inverted(self):
        return GenericTransform(np.linalg.inv(self.matrix))



# add an image to the plot with a transformation
def addImage(filename, matrix):
    transform = GenericTransform(matrix)
    img = mpimg.imread(filename)
    imgShow = plt.imshow(img, alpha=1)
    imgShow.set_transform(transform + imgShow.get_transform())

I call addImage with an image filename and a matrix, then call plt.show(), and the transformation seems to be applied correctly. However, the image that gets drawn appears to be extended beyond what its bounds should be, as if to fill the axis-aligned box defined by the image's corners: transformed image goes beyond expected bounds The line where there appears to be a "reflection" is where I want the image to simply be cut off. I'm not too familiar with matplotlib, so I'm confused as to why this happens. I was loosely following this. I also tried manually clip the image using a path:

# add an image to the plot with a transformation
def addImage(filename, matrix):
    transform = GenericTransform(matrix)
    img = mpimg.imread(filename)
    x = img.shape[1]
    y = img.shape[0]
    path = transform.transform_path(mp.path.Path([[0,0], [x,0], [x,y], [0,y], [0,0]]))
    patch = mp.patches.PathPatch(path, facecolor="none")
    imgShow = plt.imshow(img, alpha=1, clip_on=True, clip_path=patch)
    imgShow.set_transform(transform + imgShow.get_transform())

but this didn't quite work how I wanted. Specifically, whenever I resized the window or moved the plot around, the clipped areas remained stationary as the image in the background moved. How can I get it to clip my image correctly?

Also, I'm aware that the way I do the transformations might be wrong for perspective transformations, but I'd like to figure that out on my own.

Upvotes: 1

Views: 608

Answers (1)

Marek Pokorny
Marek Pokorny

Reputation: 153

After a few hours, I finally figured it out. I didn't quite understand how transformations were being applied to the clip path. Ultimately, this worked:

def addImage(filename, matrix):
    transform = GenericTransform(matrix)
    img = mpimg.imread(filename)
    imgShow = plt.imshow(img, alpha=1, clip_on=True, interpolation="bilinear")
    oldTransform = imgShow.get_transform()
    imgShow.set_transform(transform + oldTransform)

    w = img.shape[1]
    h = img.shape[0]
    path = transform.transform_path(mp.path.Path([[0,0], [w,0], [w,h], [0,h], [0,0]]))
    patch = mp.patches.PathPatch(path, facecolor="none")
    patch.set_transform(oldTransform)
    imgShow.set_clip_path(patch)

Upvotes: 2

Related Questions