"""Generate the tutorial figures. Python 3 + numpy + opencv-python + matplotlib.
Usage: python lena_demo.py --input Lena.jpg --output results
Figure labels use English so no extra font installation is needed.
"""
from pathlib import Path
import argparse
import cv2
import numpy as np
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt


def main():
    parser = argparse.ArgumentParser()
    parser.add_argument('--input', type=Path, default=Path('Lena.jpg'))
    parser.add_argument('--output', type=Path, default=Path('results'))
    args = parser.parse_args()
    bgr = cv2.imread(str(args.input))
    if bgr is None:
        raise FileNotFoundError(f'Cannot read {args.input.resolve()}')
    args.output.mkdir(parents=True, exist_ok=True)
    rgb = cv2.cvtColor(bgr, cv2.COLOR_BGR2RGB)
    gray = cv2.cvtColor(bgr, cv2.COLOR_BGR2GRAY)

    def sheet(name, panels, cols=3):
        rows = (len(panels) + cols - 1) // cols
        fig, axes = plt.subplots(rows, cols, figsize=(cols * 3.4, rows * 3.65), squeeze=False)
        for ax, (label, image) in zip(axes.flat, panels):
            ax.imshow(image, cmap='gray', vmin=0, vmax=255)
            ax.set_title(label, fontsize=11, pad=10)
            ax.axis('off')
        for ax in axes.flat[len(panels):]:
            ax.axis('off')
        fig.tight_layout(pad=1.3)
        fig.savefig(args.output / f'{name}.jpg', dpi=140, facecolor='white', pil_kwargs={'quality':90})
        plt.close(fig)

    sheet('01-color', [('Original RGB', rgb), ('Grayscale', gray), ('BGR shown as RGB (wrong)', bgr),
                       ('R channel', rgb[:,:,0]), ('G channel', rgb[:,:,1]), ('B channel', rgb[:,:,2])])
    h, w = gray.shape
    crop = rgb[h//4:3*h//4, w//4:3*w//4]
    small = cv2.resize(rgb, (32,32), interpolation=cv2.INTER_AREA)
    sheet('02-geometry', [('Original',rgb), ('Center crop (enlarged)',cv2.resize(crop,(w,h))),
                         ('Horizontal flip',cv2.flip(rgb,1)), ('Rotate 90 clockwise',cv2.rotate(rgb,cv2.ROTATE_90_CLOCKWISE)),
                         ('32x32, nearest upsample',cv2.resize(small,(w,h),interpolation=cv2.INTER_NEAREST)),
                         ('32x32, linear upsample',cv2.resize(small,(w,h),interpolation=cv2.INTER_LINEAR))])
    low = np.clip(gray.astype(np.float32)*0.35+70,0,255).astype(np.uint8)
    equalized=cv2.equalizeHist(low)
    clahe=cv2.createCLAHE(clipLimit=2.0,tileGridSize=(8,8)).apply(low)
    sheet('03-contrast',[('Original grayscale',gray),('Low contrast: 0.35*x+70',low),('Global equalization',equalized),('CLAHE: clip=2, grid=8x8',clahe),('Brightness: x+50 (clipped)',np.clip(gray.astype(np.float32)+50,0,255).astype(np.uint8)),('Negative: 255-x',255-gray)])
    fig,axes=plt.subplots(1,3,figsize=(10.2,3))
    for ax,label,img in zip(axes,['Low contrast','Equalized','CLAHE'],[low,equalized,clahe]):
        ax.hist(img.ravel(),bins=256,range=(0,256),color='#1688b8');ax.set_title(label);ax.set_xlim(0,255);ax.set_xlabel('Gray value');ax.set_ylabel('Pixels')
    fig.tight_layout();fig.savefig(args.output/'04-histograms.jpg',dpi=140);plt.close(fig)
    rng=np.random.default_rng(42)
    noisy=np.clip(gray.astype(np.float32)+rng.normal(0,20,gray.shape),0,255).astype(np.uint8)
    salt=gray.copy();r=rng.random(gray.shape);salt[r<0.025]=0;salt[r>0.975]=255
    sheet('05-denoise',[('Original grayscale',gray),('Gaussian noise: sigma=20',noisy),('Gaussian blur: 5x5, sigma=1.2',cv2.GaussianBlur(noisy,(5,5),1.2)),('Bilateral: d=9, 50, 50',cv2.bilateralFilter(noisy,9,50,50)),('Salt & pepper: 5%',salt),('Median blur: 5x5',cv2.medianBlur(salt,5))])
    _,fixed=cv2.threshold(gray,127,255,cv2.THRESH_BINARY)
    otsu_t,otsu=cv2.threshold(gray,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)
    adaptive=cv2.adaptiveThreshold(gray,255,cv2.ADAPTIVE_THRESH_GAUSSIAN_C,cv2.THRESH_BINARY,21,5)
    kernel=np.ones((3,3),np.uint8)
    sheet('06-threshold',[('Original grayscale',gray),('Fixed threshold: 127',fixed),(f'Otsu threshold: {otsu_t:.0f}',otsu),('Adaptive: block=21, C=5',adaptive),('Opening: 3x3 (on fixed)',cv2.morphologyEx(fixed,cv2.MORPH_OPEN,kernel)),('Closing: 3x3 (on fixed)',cv2.morphologyEx(fixed,cv2.MORPH_CLOSE,kernel))])
    smooth=cv2.GaussianBlur(gray,(5,5),1.2)
    sx=cv2.Sobel(smooth,cv2.CV_32F,1,0,ksize=3);sy=cv2.Sobel(smooth,cv2.CV_32F,0,1,ksize=3)
    mag=cv2.normalize(cv2.magnitude(sx,sy),None,0,255,cv2.NORM_MINMAX).astype(np.uint8)
    edges=cv2.Canny(smooth,80,160)
    overlay=rgb.copy();overlay[edges>0]=(0,255,255)
    sheet('07-edges',[('Original grayscale',gray),('Sobel magnitude (normalized)',mag),('Canny: 40 / 80',cv2.Canny(smooth,40,80)),('Canny: 80 / 160',edges),('Canny: 120 / 240',cv2.Canny(smooth,120,240)),('Edges over original',overlay)])
    spectrum=np.log1p(np.abs(np.fft.fftshift(np.fft.fft2(gray))))
    spectrum=cv2.normalize(spectrum,None,0,255,cv2.NORM_MINMAX).astype(np.uint8)
    blurred=cv2.GaussianBlur(gray,(0,0),3)
    sharpened=np.clip(gray.astype(np.float32)*1.8-blurred.astype(np.float32)*0.8,0,255).astype(np.uint8)
    sheet('08-frequency',[('Original grayscale',gray),('FFT log magnitude (normalized)',spectrum),('Unsharp mask: amount=0.8',sharpened)])
    print(f'Generated 8 figure sheets from {w}x{h} input; Otsu threshold={otsu_t:.0f}; OpenCV={cv2.__version__}')

if __name__ == '__main__':
    main()
