Code:


import numpy as np
from PIL import Image
 
 
def save_image(arr, filename):
    """Save a numpy array as an image file."""
    Image.fromarray(arr.astype("uint8")).save(filename)
 
 
# ── Exercise 1: Why NumPy? ────────────────────────────────────────────────────
 
print("=" * 60)
print("Exercise 1: Why NumPy?")
print("=" * 60)
 
# (a) list * 2 repeats the list, it does NOT multiply each element
l = [1, 2, 3, 4]
print("(a) l * 2 =", l * 2)
# Output: [1, 2, 3, 4, 1, 2, 3, 4]  — the list is duplicated, not scaled
 
# (b) l + 5 raises TypeError: can only concatenate list (not "int") to list
#     Python lists only support + with another list, not with a number.
# (uncomment to see the error)
# print(l + 5)
 
# (c) NumPy element-wise operations
a = np.array([1, 2, 3, 4])
print("(c) a * 2 =", a * 2)   # [2, 4, 6, 8]
print("(c) a + 5 =", a + 5)   # [6, 7, 8, 9]
 
# (d) Written answer:
# NumPy arrays support element-wise arithmetic directly, whereas Python lists
# treat * as repetition and + as concatenation, making them unsuitable for
# numerical computations on images.
 
 
# ── Exercise 2: Array Shape and Dimensions ────────────────────────────────────
 
print("\n" + "=" * 60)
print("Exercise 2: Array Shape and Dimensions")
print("=" * 60)
 
# (a) 1D array
a = np.array([10, 20, 30, 40, 50])
print("(a) shape:", a.shape)   # (5,)
print("(a) dtype:", a.dtype)   # int64 (platform-dependent)
 
# (b) 2D array — 2 rows, 3 columns
m = np.array([[1, 2, 3],
              [4, 5, 6]])
print("(b) shape:", m.shape)   # (2, 3)
# 2 rows, 3 columns
 
# (c) Pre-filled arrays
z = np.zeros((4, 5))
o = np.ones((3, 3))
f = np.full((3, 3), 128)
print("(c) np.zeros dtype:", z.dtype)   # float64 — the default
print("(c) np.full:\n", f)
 
# (d) int16 grayscale image
gray = np.zeros((4, 6), dtype=np.int16)
print("(d) dtype:", gray.dtype)   # int16
print("(d) shape:", gray.shape)   # (4, 6)
# int16 is used because blur computations may produce values outside [0, 255]
# and can be negative; uint8 would silently wrap around.
 
 
# ── Exercise 3: Indexing and Slicing ─────────────────────────────────────────
 
print("\n" + "=" * 60)
print("Exercise 3: Indexing and Slicing")
print("=" * 60)
 
m = np.array([[10, 20, 30],
              [40, 50, 60],
              [70, 80, 90]])
 
# (a) Individual element access
print("(a) m[1,1] =", m[1, 1])   # 50
print("(a) m[0,2] =", m[0, 2])   # 30
print("(a) m[2,0] =", m[2, 0])   # 70
 
# (b) Rows and columns
print("(b) first row:     ", m[0, :])    # [10, 20, 30]
print("(b) second column: ", m[:, 1])    # [20, 50, 80]
print("(b) last row:      ", m[-1, :])   # [70, 80, 90]
 
# (c) Rectangular sub-region
sub = m[1:3, 1:3]
print("(c) sub-region:\n", sub)
# [[50, 60],
#  [80, 90]]
 
# (d) Write to a region — top-left 2×2 block becomes 0
m_copy = m.copy()
m_copy[0:2, 0:2] = 0
print("(d) after writing 0 to [0:2, 0:2]:\n", m_copy)
# The four values m[0,0]=10, m[0,1]=20, m[1,0]=40, m[1,1]=50 are now 0.
 
# (e) Self-check slices on a 5×5 array
a5 = np.arange(25).reshape(5, 5)
print("(e) 5×5 array:\n", a5)
print("(e) second row:               ", a5[1, :])
print("(e) last two columns:\n",         a5[:, 3:])
print("(e) 3×3 block at [1:4, 1:4]:\n", a5[1:4, 1:4])
 
 
# ── Exercise 4: Element-Wise Operations and Comparing Arrays ──────────────────
 
print("\n" + "=" * 60)
print("Exercise 4: Element-Wise Operations and Comparing Arrays")
print("=" * 60)
 
# (a) Arithmetic
a = np.array([1, 2, 3])
b = np.array([10, 20, 30])
print("(a) a + b =", a + b)   # [11, 22, 33]
print("(a) a * b =", a * b)   # [10, 40, 90]
print("(a) b - a =", b - a)   # [ 9, 18, 27]
 
# (b) Subtracting two image regions — result can be negative
img1 = np.array([[100, 150],
                 [200,  50]], dtype=np.int16)
img2 = np.array([[ 80, 160],
                 [210,  40]], dtype=np.int16)
diff = img1 - img2
print("(b) diff:\n", diff)
# [[ 20, -10],
#  [-10,  10]]
# With uint8: 150 - 160 would wrap around to 246 (256 + 150 - 160 = 246)
# because uint8 cannot represent negative values — the result would be wrong.
 
# (c) Absolute difference
abs_diff = np.abs(img1 - img2)
print("(c) abs_diff:\n", abs_diff)
# [[20, 10],
#  [10, 10]]
 
# (d) Mean
print("(d) mean of abs_diff:", np.mean(abs_diff))   # 12.5
 
m = np.array([[1, 2, 3],
              [4, 5, 6]])
print("(d) mean of all elements:", np.mean(m))       # 3.5
print("(d) mean of first row:   ", np.mean(m[0, :])) # 2.0
 
# (e) Similarity score
zone1 = np.array([[100, 120, 130]], dtype=np.int16)
zone2 = np.array([[ 90, 115, 135]], dtype=np.int16)
score = float(np.mean(np.abs(zone1 - zone2))) / 255.0
print("(e) score (different):", score)   # ≈ 0.033
 
# Verify: identical zones → score = 0.0
zone_same = np.array([[100, 120]], dtype=np.int16)
print("(e) score (identical):", float(np.mean(np.abs(zone_same - zone_same))) / 255.0)  # 0.0
 
# Verify: all-0 vs all-255 → score = 1.0
zeros = np.array([[0, 0]], dtype=np.int16)
maxv  = np.array([[255, 255]], dtype=np.int16)
print("(e) score (max diff): ", float(np.mean(np.abs(zeros - maxv))) / 255.0)  # 1.0
 
 
# ── Exercise 5: Creating and Drawing on Grayscale Images ─────────────────────
 
print("\n" + "=" * 60)
print("Exercise 5: Creating and Drawing on Grayscale Images")
print("=" * 60)
 
# (a) Black image
img = np.zeros((10, 10), dtype=np.uint8)
save_image(img, "black.png")
print("(a) Saved black.png — a fully black 10×10 image.")
 
# (b) Vertical gradient
img = np.zeros((10, 10), dtype=np.uint8)
for row in range(10):
    for col in range(10):
        img[row, col] = (row + 1) * 20
save_image(img, "gradient.png")
print("(b) Saved gradient.png — gradient goes top (dark) to bottom (bright).")
print("    To make it horizontal: use (col + 1) * 20 instead of (row + 1) * 20.")
 
# (c) Rectangle using loops
img = np.zeros((10, 10), dtype=np.uint8)
for col in range(2, 8):
    img[2, col] = 255
for col in range(2, 8):
    img[7, col] = 255
for row in range(3, 7):
    img[row, 2] = 255
for row in range(3, 7):
    img[row, 7] = 255
save_image(img, "box_loops.png")
print("(c) Saved box_loops.png")
 
# (d) Same rectangle using slices — shorter and faster
img = np.zeros((10, 10), dtype=np.uint8)
img[2, 2:8] = 255    # top edge
img[7, 2:8] = 255    # bottom edge
img[3:7, 2] = 255    # left edge
img[3:7, 7] = 255    # right edge
save_image(img, "box_slices.png")
print("(d) Saved box_slices.png — identical result, 4 lines vs 8 lines.")
 
 
# ── Exercise 6: Colour Images ─────────────────────────────────────────────────
 
print("\n" + "=" * 60)
print("Exercise 6: Colour Images")
print("=" * 60)
 
# (a) Black colour image
img = np.zeros((250, 250, 3), dtype=np.uint8)
print("(a) shape:", img.shape)         # (250, 250, 3)
print("(a) centre pixel:", img[125, 125])  # [0, 0, 0]
 
# (b) RGB gradient
for row in range(250):
    for col in range(250):
        img[row, col] = [row, 0, col]
save_image(img, "gradient_rgb.png")
print("(b) Saved gradient_rgb.png")
print("    top-left  (0,0):   black  [0, 0, 0]")
print("    top-right (0,249): blue   [0, 0, 249]")
print("    bot-left  (249,0): red    [249, 0, 0]")
 
# (c) Extract a channel
red_channel  = img[:, :, 0]
blue_channel = img[:, :, 2]   # ← answer: index 2 for blue
print("(c) red channel shape: ", red_channel.shape)   # (250, 250)
print("(c) blue channel shape:", blue_channel.shape)  # (250, 250)
 
# (d) Zero out the green channel
img[:, :, 1] = 0
save_image(img, "no_green.png")
print("(d) Saved no_green.png — green channel removed.")
 
# (e) Modify a single pixel
print("(e) img[0,0] before:", img[0, 0])
img[0, 0] = [255, 255, 0]    # pure yellow
print("(e) img[0,0] after: ", img[0, 0])  # [255, 255, 0]
 
Modifié le: lundi 20 avril 2026, 13:37