Katkat

Implement a Batch Image Processor

AnthropicSoftware Engineer · Technical Screen

Last updated

Design and implement an in-memory image transformation engine that applies sequential operations (grayscale, brightness scaling, nearest-neighbor resizing) over a batch of images efficiently.

Problem Statement

You are tasked with building a lightweight, CPU-based batch image processing pipeline without external libraries like OpenCV or PIL. An image is represented as a 3D array of integers of shape (height, width, 3), where each pixel contains [R, G, B] values bounded by [0, 255].

Implement the class BatchImageProcessor with methods for individual operations and a batch runner:

  1. to_grayscale(image): Converts an RGB image into grayscale. To keep array shapes consistent along the pipeline, output an image with 3 identical channels: [Y, Y, Y], where luminance is calculated as: Y = int(0.299 * R + 0.587 * G + 0.114 * B)
  2. adjust_brightness(image, factor): Multiplies every RGB value by a float factor and clamps the resulting integer to [0, 255].
  3. resize(image, new_h, new_w): Resizes an image using nearest-neighbor interpolation. The pixel at target coordinate (r, c) maps to the source coordinate (int(r * orig_h / new_h), int(c * orig_w / new_w)).
  4. process_batch(images, pipeline, max_workers): Applies a list of transformations sequentially to each image in images. For CPU-bound workloads, leverage parallel execution across max_workers.

Function Signatures

class BatchImageProcessor:
    @staticmethod
    def to_grayscale(image: list[list[list[int]]]) -> list[list[list[int]]]:
        pass

    @staticmethod
    def adjust_brightness(image: list[list[list[int]]], factor: float) -> list[list[list[int]]]:
        pass

    @staticmethod
    def resize(image: list[list[list[int]]], new_h: int, new_w: int) -> list[list[list[int]]]:
        pass

    def process_batch(
        self,
        images: list[list[list[list[int]]]],
        pipeline: list[tuple],
        max_workers: int = 1
    ) -> list[list[list[list[int]]]]:
        pass

Example

processor = BatchImageProcessor()

# Single 2x2 RGB image
img = [
    [[100, 150, 200], [50, 50, 50]],
    [[0, 255, 0], [255, 255, 255]]
]

# Brightness scaled by 1.5
scaled = processor.adjust_brightness(img, 1.5)
# Top-left pixel: [100 * 1.5, 150 * 1.5, 200 * 1.5] = [150, 225, 255] (clamped)

# Resize to 1x1
resized = processor.resize(img, 1, 1)
# Maps to source (0, 0) -> [[[100, 150, 200]]]

Constraints

  • Image dimensions: 1 = 0.0
  • new_h, new_w >= 1
  • Pipeline operations are passed as tuples, e.g., ("brightness", 1.2) or ("grayscale",)

Hints

Hint 1

Ensure each pixel transformation clamps values strictly to [0, 255] before integer conversion.

Hint 2

For nearest-neighbor resizing, avoid floating point rounding errors by computing the source index using integer division on `r * orig_h // new_h`.

Hint 3

Because image processing is strictly CPU-bound in Python, using `concurrent.futures.ProcessPoolExecutor` bypasses the GIL, whereas `ThreadPoolExecutor` will serialize execution.

Follow-up questions

  • Passing large raw pixel buffers between processes incurs significant IPC serialization overhead. How would you mitigate this using shared memory (e.g., `multiprocessing.shared_memory`)?
  • How would you implement operator fusion (combining grayscale and brightness in a single pass) to improve CPU cache locality?
  • How does nearest-neighbor interpolation compare to bilinear interpolation in terms of algorithmic complexity and visual artifacts like aliasing?