Creates a 4x2 plot for analyzing focal designs of artworks.
Code:
import numpy as np
from PIL import Image, ImageEnhance, ImageOps
import cv2
import matplotlib.pyplot as plt
from matplotlib.colors import LinearSegmentedColormap
from scipy.ndimage import generic_filter
# ---- CONFIG ----
IMAGE_PATH = "your_image.png" # Replace with your image path
SATURATION_THRESHOLD = 40 # To mask out low-saturation areas in hue map
LOCAL_CONTRAST_WINDOW = 11 # Size for local contrast (odd integer)
FIGSIZE = (22, 12) # High-res figure size
# ---- LOAD AND PREP ----
image = Image.open(IMAGE_PATH).convert("RGB")
image_np = np.array(image)
hsv = cv2.cvtColor(image_np, cv2.COLOR_RGB2HSV)
hue, sat, val = cv2.split(hsv)
gray = cv2.cvtColor(image_np, cv2.COLOR_RGB2GRAY)
# ---- HUE MAP ----
mask = sat > SATURATION_THRESHOLD
hue_map_hsv = np.zeros_like(hsv)
hue_map_hsv[..., 0] = hue
hue_map_hsv[..., 1] = 255
hue_map_hsv[..., 2] = 255
hue_map_rgb = cv2.cvtColor(hue_map_hsv, cv2.COLOR_HSV2RGB)
hue_map_rgb[~mask] = np.mean(image_np[~mask], axis=-1, keepdims=True).astype(np.uint8)
# ---- SATURATION MAP ----
sat_norm = (sat / 255 * 255).astype(np.uint8)
sat_rgb = cv2.cvtColor(cv2.merge([np.zeros_like(sat), np.zeros_like(sat), sat_norm]), cv2.COLOR_BGR2RGB)
# ---- BRIGHTNESS MAP ----
val_rgb = cv2.cvtColor(cv2.merge([val, val, val]), cv2.COLOR_BGR2RGB)
# ---- EDGE MAP ----
edges = cv2.Canny(gray, 100, 200)
edges_rgb = np.stack([edges]*3, axis=-1)
# ---- LOCAL CONTRAST MAP ----
def local_std(arr):
return np.std(arr)
local_contrast = generic_filter(gray.astype(np.float32), local_std, size=LOCAL_CONTRAST_WINDOW)
local_contrast_norm = ((local_contrast - local_contrast.min()) / (local_contrast.ptp()) * 255).astype(np.uint8)
local_contrast_rgb = np.stack([local_contrast_norm]*3, axis=-1)
# ---- COLOR TEMPERATURE MAP ----
red = image_np[..., 0].astype(np.float32)
blue = image_np[..., 2].astype(np.float32)
temp_ratio = (red - blue) / (red + blue + 1e-6) # avoid div by zero
temp_norm = (temp_ratio - temp_ratio.min()) / (temp_ratio.ptp())
temp_cmap = LinearSegmentedColormap.from_list("temp_cmap", ["#00aaff", "#ffffff", "#ffaa00"])
temp_rgb = (temp_cmap(temp_norm)[:, :, :3] * 255).astype(np.uint8)
# ---- PLOT 2x4 GRID ----
fig, axes = plt.subplots(2, 4, figsize=FIGSIZE)
# Top row
axes[0, 0].imshow(image_np)
axes[0, 0].set_title("Original", fontsize=16)
axes[0, 1].imshow(hue_map_rgb)
axes[0, 1].set_title("Hue Map", fontsize=16)
axes[0, 2].imshow(sat_rgb)
axes[0, 2].set_title("Saturation Map", fontsize=16)
axes[0, 3].imshow(temp_rgb)
axes[0, 3].set_title("Color Temperature Map", fontsize=16)
# Bottom row
axes[1, 0].axis("off") # left blank
axes[1, 1].imshow(val_rgb)
axes[1, 1].set_title("Brightness Map", fontsize=16)
axes[1, 2].imshow(edges_rgb)
axes[1, 2].set_title("Edge Map", fontsize=16)
axes[1, 3].imshow(local_contrast_rgb)
axes[1, 3].set_title("Local Contrast Map", fontsize=16)
for ax in axes.flatten():
ax.axis("off")
plt.tight_layout()
plt.show()