Practical 3: Negation, Subtraction & Inversion
Run this practical in Google Colab
Objective
To implement and analyze fundamental intensity transformation operations: image negation, image subtraction, and intensity inversion.
2. Description / Theory
Theory: Intensity transformations are point operations that modify pixel values individually.
Negation reverses the intensity scale: \(s = (L-1) - r\), useful for enhancing detail in dark regions.
Image subtraction \(g = |f_1 - f_2|\) reveals differences between two images, widely used in medical imaging (Digital Subtraction Angiography).
# ----------------------------------------------------------------------
# Install dependencies (uncomment for Google Colab)
# !pip install opencv-python-headless matplotlib numpy
import cv2
import numpy as np
import matplotlib.pyplot as plt
import os
# === AUTO-DOWNLOAD DATASET (works in Google Colab and locally) ===
import os, urllib.request, zipfile
# Choose which chapter to download (CH01-CH12)
CHAPTER = "CH02" # Chapter 2: Digital Image Fundamentals
DATASET_PATH = f"datasets/{CHAPTER}/"
DOWNLOAD_BASE = "https://www.imageprocessingplace.com/downloads_V3/dip3e_downloads/dip3e_book_images"
if not os.path.exists(DATASET_PATH) or not any(f.endswith('.tif') for f in os.listdir(DATASET_PATH)):
zip_name = f"DIP3E_{CHAPTER}_Original_Images.zip"
url = f"{DOWNLOAD_BASE}/{zip_name}"
print(f"Downloading {CHAPTER} dataset from imageprocessingplace.com...")
urllib.request.urlretrieve(url, "chapter.zip")
os.makedirs(DATASET_PATH, exist_ok=True)
with zipfile.ZipFile("chapter.zip", "r") as z:
for f in z.namelist():
if f.lower().endswith(".tif"):
fname = os.path.basename(f)
if fname:
with z.open(f) as src, open(os.path.join(DATASET_PATH, fname), "wb") as dst:
dst.write(src.read())
os.remove("chapter.zip")
print(f"Downloaded {len([f for f in os.listdir(DATASET_PATH) if f.endswith('.tif')])} images")
else:
print(f"Dataset ready: {len([f for f in os.listdir(DATASET_PATH) if f.endswith('.tif')])} images")
# List all available images
images = sorted([f for f in os.listdir(DATASET_PATH) if f.endswith('.tif')])
print(f"\nAvailable images ({len(images)}):")
for i, name in enumerate(images, 1):
print(f" {i}. {name}")
# === SELECT YOUR IMAGE HERE ===
selected_image = "Fig0232(a)(partial_body_scan).tif" # Change to any image
# Load the image
img_color = cv2.imread(os.path.join(DATASET_PATH, selected_image))
img_gray = cv2.cvtColor(img_color, cv2.COLOR_BGR2GRAY)
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(10, 5))
ax1.imshow(cv2.cvtColor(img_color, cv2.COLOR_BGR2RGB))
ax1.set_title("Original (Color)")
ax1.axis('off')
ax2.imshow(img_gray, cmap='gray')
ax2.set_title("Grayscale")
ax2.axis('off')
plt.suptitle("Grayscale Conversion", fontsize=14, fontweight='bold')
plt.tight_layout()
plt.show()
print(f"Color shape: {img_color.shape}, Grayscale shape: {img_gray.shape}")
# === SELECT IMAGE FOR NEGATION ===
negation_image = "Fig0232(a)(partial_body_scan).tif" # Change to any image
img = cv2.imread(os.path.join(DATASET_PATH, negation_image), cv2.IMREAD_GRAYSCALE)
# Compute negative: s = 255 - r
negative = 255 - img
fig, axes = plt.subplots(2, 2, figsize=(12, 10))
# Original image
axes[0, 0].imshow(img, cmap='gray')
axes[0, 0].set_title("Original Image")
axes[0, 0].axis('off')
# Negative image
axes[0, 1].imshow(negative, cmap='gray')
axes[0, 1].set_title("Negative (s = 255 - r)")
axes[0, 1].axis('off')
# Original histogram
axes[1, 0].hist(img.ravel(), bins=256, range=(0, 256), color='black', alpha=0.7)
axes[1, 0].set_title("Original Histogram")
axes[1, 0].set_xlabel("Intensity")
axes[1, 0].set_ylabel("Frequency")
# Negative histogram
axes[1, 1].hist(negative.ravel(), bins=256, range=(0, 256), color='black', alpha=0.7)
axes[1, 1].set_title("Negative Histogram")
axes[1, 1].set_xlabel("Intensity")
axes[1, 1].set_ylabel("Frequency")
plt.suptitle(f"Image Negation: {negation_image}", fontsize=14, fontweight='bold')
plt.tight_layout()
plt.show()
# === ANGIOGRAPHY IMAGE PAIR (change to any two images) ===
mask_file = "Fig0228(a)(angiography_mask_image).tif"
live_file = "Fig0228(b)(angiography_live_ image).tif"
mask = cv2.imread(os.path.join(DATASET_PATH, mask_file), cv2.IMREAD_GRAYSCALE)
live = cv2.imread(os.path.join(DATASET_PATH, live_file), cv2.IMREAD_GRAYSCALE)
# Ensure same size
if mask.shape != live.shape:
live = cv2.resize(live, (mask.shape[1], mask.shape[0]))
# Compute absolute difference
difference = cv2.absdiff(live, mask)
fig, axes = plt.subplots(1, 3, figsize=(15, 5))
axes[0].imshow(mask, cmap='gray')
axes[0].set_title("Mask Image (Before Contrast)")
axes[0].axis('off')
axes[1].imshow(live, cmap='gray')
axes[1].set_title("Live Image (After Contrast)")
axes[1].axis('off')
axes[2].imshow(difference, cmap='gray')
axes[2].set_title("Subtraction: |Live - Mask|")
axes[2].axis('off')
plt.suptitle("Digital Subtraction Angiography", fontsize=14, fontweight='bold')
plt.tight_layout()
plt.show()
# === DENTAL IMAGE PAIR (change to any two images) ===
dental_file = "Fig0230(a)(dental_xray).tif"
dental_mask_file = "Fig0230(b)(dental_xray_mask).tif"
dental = cv2.imread(os.path.join(DATASET_PATH, dental_file), cv2.IMREAD_GRAYSCALE)
dental_mask = cv2.imread(os.path.join(DATASET_PATH, dental_mask_file), cv2.IMREAD_GRAYSCALE)
if dental.shape != dental_mask.shape:
dental_mask = cv2.resize(dental_mask, (dental.shape[1], dental.shape[0]))
dental_diff = cv2.absdiff(dental, dental_mask)
fig, axes = plt.subplots(1, 3, figsize=(15, 5))
axes[0].imshow(dental, cmap='gray')
axes[0].set_title("Dental X-ray")
axes[0].axis('off')
axes[1].imshow(dental_mask, cmap='gray')
axes[1].set_title("Dental Mask")
axes[1].axis('off')
axes[2].imshow(dental_diff, cmap='gray')
axes[2].set_title("Subtraction: |Xray - Mask|")
axes[2].axis('off')
plt.suptitle("Dental X-ray Subtraction", fontsize=14, fontweight='bold')
plt.tight_layout()
plt.show()
# === TUNGSTEN IMAGE PAIR (change to any two images) ===
filament_file = "Fig0229(a)(tungsten_filament_shaded).tif"
shading_file = "Fig0229(b)(tungsten_sensor_shading).tif"
filament = cv2.imread(os.path.join(DATASET_PATH, filament_file), cv2.IMREAD_GRAYSCALE)
shading = cv2.imread(os.path.join(DATASET_PATH, shading_file), cv2.IMREAD_GRAYSCALE)
if filament.shape != shading.shape:
shading = cv2.resize(shading, (filament.shape[1], filament.shape[0]))
# Shading correction via subtraction
corrected = cv2.absdiff(filament, shading)
fig, axes = plt.subplots(1, 3, figsize=(15, 5))
axes[0].imshow(filament, cmap='gray')
axes[0].set_title("Shaded Filament")
axes[0].axis('off')
axes[1].imshow(shading, cmap='gray')
axes[1].set_title("Sensor Shading Pattern")
axes[1].axis('off')
axes[2].imshow(corrected, cmap='gray')
axes[2].set_title("Corrected: |Filament - Shading|")
axes[2].axis('off')
plt.suptitle("Shading Correction", fontsize=14, fontweight='bold')
plt.tight_layout()
plt.show()
# Invert the angiography subtraction result
inverted_diff = 255 - difference
fig, axes = plt.subplots(1, 3, figsize=(15, 5))
axes[0].imshow(difference, cmap='gray')
axes[0].set_title("Subtraction Result")
axes[0].axis('off')
axes[1].imshow(inverted_diff, cmap='gray')
axes[1].set_title("Inverted Subtraction (255 - diff)")
axes[1].axis('off')
# Enhanced version with contrast stretching
enhanced = cv2.normalize(difference, None, 0, 255, cv2.NORM_MINMAX)
axes[2].imshow(enhanced, cmap='gray')
axes[2].set_title("Enhanced (Contrast Stretched)")
axes[2].axis('off')
plt.suptitle("Inversion and Enhancement Pipeline", fontsize=14, fontweight='bold')
plt.tight_layout()
plt.show()
# Full pipeline on angiography images
fig, axes = plt.subplots(2, 3, figsize=(15, 10))
axes[0, 0].imshow(mask, cmap='gray')
axes[0, 0].set_title("1. Mask Image")
axes[0, 0].axis('off')
axes[0, 1].imshow(live, cmap='gray')
axes[0, 1].set_title("2. Live Image")
axes[0, 1].axis('off')
axes[0, 2].imshow(difference, cmap='gray')
axes[0, 2].set_title("3. |Live - Mask|")
axes[0, 2].axis('off')
axes[1, 0].imshow(255 - mask, cmap='gray')
axes[1, 0].set_title("4. Negation of Mask")
axes[1, 0].axis('off')
axes[1, 1].imshow(inverted_diff, cmap='gray')
axes[1, 1].set_title("5. Inverted Difference")
axes[1, 1].axis('off')
axes[1, 2].imshow(enhanced, cmap='gray')
axes[1, 2].set_title("6. Enhanced Result")
axes[1, 2].axis('off')
plt.suptitle("Complete Pipeline: Subtraction -> Negation -> Enhancement", fontsize=14, fontweight='bold')
plt.tight_layout()
plt.show()
Part 1: Image Negation (s = 255 - r)
Compute the negative of an image by inverting all pixel intensities.
Part 2: Image Subtraction |Img1 - Img2|
Compute the absolute difference between two images to reveal hidden structures.
Part 3: Complete Pipeline (Subtraction → Negation → Enhancement)
Run the full pipeline on the selected image pair: subtraction, then negation of result, then contrast enhancement.
Analysis Questions
- In the angiography example, what structures become visible after subtraction that were not visible in either original image?
- Why is absolute difference used instead of simple subtraction? What would happen with signed vs unsigned arithmetic?
- Compare the histogram of the original image with its negative. What mathematical relationship exists between them?