Homework 03: Laplacian Pyramids

Build, reconstruct, and compress Laplacian image pyramids.

Total: 5 points
Submission: Gradescope Programming Assignment
Work: Individual

Objective

In this homework, you will implement a compact multiscale image representation based on the Laplacian pyramid. You will:

  • construct a Laplacian pyramid from a grayscale image;
  • reconstruct the original image from the stored residuals and coarsest Gaussian level;
  • create a lossy representation by discarding small residual coefficients; and
  • quantify how sparse the residual representation becomes after thresholding.

Use the course cv Conda environment and OpenCV (cv2) for the pyramid operations described below.

Do not use generative AI to solve this homework. The goal is to make sure you understand how multiscale residual representations are constructed and reconstructed.

Starter file

Complete the provided hw03.py file. Do not rename the required functions or change their arguments because the Gradescope autograder calls them directly.

Download hw03.py

The required functions are:

build_laplacian_pyramid(image, levels)
reconstruct_laplacian_pyramid(pyramid)
threshold_laplacian_pyramid(pyramid, threshold)
residual_nonzero_fraction(pyramid)

Part 1 — Build a Laplacian pyramid

Complete:

def build_laplacian_pyramid(image, levels):
    ...

The input is a 2-D grayscale numpy.float32 image with intensity values in [0, 1]. The argument levels is the number of residual levels to construct.

Return a list with this structure:

[L0, L1, ..., L_(levels-1), G_levels]

At each level:

  1. Smooth and downsample the current Gaussian image using cv2.pyrDown(..., borderType=cv2.BORDER_REFLECT).
  2. Expand the coarse image back to the exact size of the current image using cv2.pyrUp(..., dstsize=current.shape[::-1]).
  3. Compute the signed residual:

\[ L_i = G_i - \operatorname{expand}(G_{i+1}). \]

  1. Continue from the coarse image.

Store the final coarse Gaussian image as the final element of the list.

Requirements:

  • all returned arrays must have dtype numpy.float32;
  • preserve signed residual values; do not clip them to [0, 1];
  • the implementation must work with odd-sized and non-square images; and
  • levels will be at least 1 in graded tests.

Part 2 — Reconstruct the image

Complete:

def reconstruct_laplacian_pyramid(pyramid):
    ...

Start from the smallest Gaussian image stored at pyramid[-1]. For each finer level:

  1. expand the current image to exactly the residual’s width and height using cv2.pyrUp() and dstsize; and
  2. add the residual at that level.

In other words,

\[ G_i = \operatorname{expand}(G_{i+1}) + L_i. \]

With an unmodified Laplacian pyramid, the reconstruction should match the original image apart from small floating-point roundoff.

Return a numpy.float32 array.

Part 3 — Threshold residuals for lossy compression

Complete:

def threshold_laplacian_pyramid(pyramid, threshold):
    ...

The residual levels often contain many values close to zero. To create a simple lossy representation, set small residual coefficients to exactly zero.

For every residual level pyramid[:-1]:

abs(coefficient) < threshold  ->  0

Important details:

  • the comparison is strictly less than the threshold;
  • do not threshold the final coarse Gaussian image;
  • return an independent copy: a new list containing a separate NumPy array for every pyramid level;
  • do not modify the original pyramid or share its arrays with the returned pyramid; and
  • return numpy.float32 arrays.

For this list-of-arrays structure, an appropriate independent copy is:

new_pyramid = [level.copy() for level in pyramid]

You may then threshold the copied residual arrays. You do not need to use copy.deepcopy().

As the threshold increases, more detail coefficients will typically become zero and the reconstructed image will lose more fine detail.

Part 4 — Measure residual sparsity

Complete:

def residual_nonzero_fraction(pyramid):
    ...

Compute

\[ \text{nonzero fraction} =\frac{\text{number of nonzero residual coefficients}} {\text{total number of residual coefficients}}. \]

Count coefficients only in pyramid[:-1]. Do not include the final coarse Gaussian image.

Return a Python float in [0, 1].

A smaller nonzero fraction means that more residual coefficients are zero and the representation is more sparse. This statistic is only a simple proxy for compressibility; it is not the actual compressed file size.

Run your program

Activate the course environment and run the script with a JPG/JPEG or PNG image. We recommend reusing your HW01 headshot so you can visually compare how increasing the threshold changes the reconstruction:

conda activate cv
python hw03.py my_image.png

The provided script constructs a three-level Laplacian pyramid and compares:

  • the original image;
  • exact reconstruction from the unmodified pyramid; and
  • lossy reconstructions after thresholding the residuals at 0.01, 0.03, and 0.08.

A successful run must:

  • print the maximum exact-reconstruction error;
  • print the residual nonzero fraction for each threshold; and
  • save a viewable hw03_output.png comparison image.

Look at how the residual nonzero fraction and reconstructed image quality change as the threshold increases.

What to submit

Submit exactly one file to Gradescope:

hw03.py

Do not submit your input image, hw03_output.png, screenshots, or a PDF report. Your headshot is only for local testing; the autograder runs your functions and script with instructor-provided images and arrays.

Submission limit — important

You may make at most 5 Gradescope submissions for this homework. Test and debug thoroughly in your local cv environment before submitting.

  • Submissions 1–5 are graded normally.
  • Submission 6 and every later submission receive a score of 0 for the homework.
  • A Gradescope infrastructure failure marked as an autograder error does not count against the five-submission limit.

Grading

HW03 is worth 5 points and is fully autograded.

The autograder checks:

  • required functions and interfaces;
  • Laplacian-pyramid construction, including odd and non-square image sizes;
  • exact reconstruction from the stored pyramid;
  • residual thresholding and preservation of the coarse image;
  • residual nonzero-fraction calculation; and
  • whether python hw03.py IMAGE runs successfully and saves the expected output file.

Some basic tests are public and provide detailed feedback when you submit. More substantive tests are private. Before the deadline, Gradescope will show only an overall pass/fail status for the private tests, without revealing which private test failed or any private-test details. The individual private tests, scores, and feedback become visible after the Gradescope late due date. Passing all public tests does not guarantee full credit.

Notes

  • Use the course cv Conda environment.
  • Use cv2.pyrDown() and cv2.pyrUp() as specified above.
  • Keep pyramid computations in floating point so signed residuals are preserved.
  • Do not change the required function names or arguments.
  • Do not hard-code outputs for a particular image, image size, number of levels, or threshold.
  • Test locally before using one of your five Gradescope submissions.