{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":6799,"databundleVersionId":4225553,"sourceType":"competition"}],"dockerImageVersionId":31089,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"This notebook is part 2 of my project:\n\nPart 1 - [https://www.kaggle.com/code/anako2020/cnn-deconvnet-part-1](http://https://www.kaggle.com/code/anako2020/cnn-deconvnet-part-1)\n\nPart2 - [https://www.kaggle.com/code/anako2020/cnn-deconvnet-part-2](http://https://www.kaggle.com/code/anako2020/cnn-deconvnet-part-2)\n\nPart3 - [https://www.kaggle.com/code/anako2020/cnn-deconvnet-part-3](http://https://www.kaggle.com/code/anako2020/cnn-deconvnet-part-3)\n","metadata":{}},{"cell_type":"markdown","source":"# Part 2\n\nIn this part I am loading AlexNet, examining it's performance and observing the network's architecture.","metadata":{}},{"cell_type":"code","source":"import numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\nimport os\nfrom PIL import Image\nimport matplotlib.pyplot as plt\nimport pandas as pd","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-16T06:36:14.396684Z","iopub.execute_input":"2025-09-16T06:36:14.397018Z","iopub.status.idle":"2025-09-16T06:36:16.453655Z","shell.execute_reply.started":"2025-09-16T06:36:14.396995Z","shell.execute_reply":"2025-09-16T06:36:16.452778Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 1. Paths\nval_path = \"/kaggle/input/imagenet-object-localization-challenge/ILSVRC/Data/CLS-LOC/val\"\nlabels_path = \"/kaggle/input/imagenet-object-localization-challenge/LOC_val_solution.csv\"\nmapping_path = \"/kaggle/input/imagenet-object-localization-challenge/LOC_synset_mapping.txt\"","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-16T06:36:16.45568Z","iopub.execute_input":"2025-09-16T06:36:16.456128Z","iopub.status.idle":"2025-09-16T06:36:16.46019Z","shell.execute_reply.started":"2025-09-16T06:36:16.456106Z","shell.execute_reply":"2025-09-16T06:36:16.459373Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Loading pretrained network","metadata":{}},{"cell_type":"code","source":"import torch\nfrom torchvision.models import alexnet\n\n# Define device\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n\n# Load pretrained model\nmodel = alexnet(weights=\"DEFAULT\").to(device)  ## ...\nmodel.eval()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-16T06:36:16.460976Z","iopub.execute_input":"2025-09-16T06:36:16.461247Z","iopub.status.idle":"2025-09-16T06:36:27.396696Z","shell.execute_reply.started":"2025-09-16T06:36:16.461223Z","shell.execute_reply":"2025-09-16T06:36:27.396026Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"AlexNet has **5 convolutional layers**, each with a different number of filters.\n\nHere’s a breakdown of the **feature extractor part** (`model.features`) of AlexNet:\n\n| Conv Layer | Model Layer | Type      | Filters | Kernel Size | Stride | Padding |\n| -----------|------------ | --------- | ------- | ----------- | ------ |-------\n| 1          | 0           | Conv2d    | 64      | 11×11       | 4      | 2       |\n| 1          | 1           | ReLU      | —       | —           | —      | —       |\n| 1          | 2           | MaxPool2d | —       | 3×3         | 2      | —       |\n| 2          | 3           | Conv2d    | 192     | 5×5         | 1      | 2       |\n| 2          | 4           | ReLU      | —       | —           | —      | —       |\n| 2          | 5           | MaxPool2d | —       | 3×3         | 2      | —       |\n| 3          | 6           | Conv2d    | 384     | 3×3         | 1      | 1       |\n| 3          | 7           | ReLU      | —       | —           | —      | —       |\n| 4          | 8           | Conv2d    | 256     | 3×3         | 1      | 1       |\n| 4          | 9           | ReLU      | —       | —           | —      | —       |\n| 5          | 10          | Conv2d    | 256     | 3×3         | 1      | 1       |\n| 5          | 11          | ReLU      | —       | —           | —      | —       |\n| 5          | 12          | MaxPool2d | —       | 3×3         | 2      | —       |\n\n### So the convolutional **filter layers** are at:\n\n* Layer 0 (`Conv2d(3 → 64)`)\n* Layer 3 (`Conv2d(64 → 192)`)\n* Layer 6 (`Conv2d(192 → 384)`)\n* Layer 8 (`Conv2d(384 → 256)`)\n* Layer 10 (`Conv2d(256 → 256)`)\n\nThere are **5 convolutional layers total**, and each has its own set of learned filters.\n\nWe need to extract and visualize those filters ...","metadata":{}},{"cell_type":"markdown","source":"## Using the loaded Alex-Net to predict 20 random images","metadata":{}},{"cell_type":"code","source":"import os\nimport random\nimport torch\nfrom torchvision import transforms\nfrom torchvision.models import alexnet, AlexNet_Weights\nfrom PIL import Image\nimport matplotlib.pyplot as plt\nimport pandas as pd\n\n# 1. Load pretrained AlexNet\n# device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n# model = alexnet(weights=\"DEFAULT\").to(device)\n# model.eval()\n\n# 2. Load val image paths\nval_path = \"/kaggle/input/imagenet-object-localization-challenge/ILSVRC/Data/CLS-LOC/val\"\nsample_imgs = random.sample(sorted(os.listdir(val_path)), 20)\n\n# 3. Load synset-to-name mapping\nlabel_map_path = \"/kaggle/input/imagenet-object-localization-challenge/LOC_synset_mapping.txt\"\nsynset_to_label = {}\nwith open(label_map_path, \"r\") as f:\n    for line in f:\n        parts = line.strip().split(\" \", 1)\n        if len(parts) == 2:\n            synset, label = parts\n            synset_to_label[synset] = label.split(\",\")[0]\n\n# 4. Load ground truth labels\ndf_labels = pd.read_csv(\"/kaggle/input/imagenet-object-localization-challenge/LOC_val_solution.csv\")\ndf_labels.columns = ['ImageId', 'Label']\ndf_labels['Label'] = df_labels['Label'].str.split().str[0]\n\n# 5. ImageNet class index → label mapping\nidx_to_label = AlexNet_Weights.IMAGENET1K_V1.meta[\"categories\"]\n\n# 6. Define transform\ntransform = transforms.Compose([\n    transforms.Resize(256),\n    transforms.CenterCrop(224),\n    transforms.ToTensor(),\n    transforms.Normalize(mean=[0.485, 0.456, 0.406],\n                         std=[0.229, 0.224, 0.225])\n])\n\n# 7. Show 20 random images with predictions\nrows, cols = 4, 5\nplt.figure(figsize=(20, 15))\n\nfor i, fname in enumerate(sample_imgs):\n    img_id = fname.split(\".\")[0]\n    img_path = os.path.join(val_path, fname)\n    img = Image.open(img_path).convert('RGB')\n    input_tensor = transform(img).unsqueeze(0).to(device)\n\n    with torch.no_grad():\n        output = model(input_tensor)\n        pred_class = output.argmax(dim=1).item()\n\n    # Predicted label\n    predicted = idx_to_label[pred_class]\n\n    # True label\n    true_synset = df_labels[df_labels[\"ImageId\"] == img_id][\"Label\"].values[0]\n    true_label = synset_to_label.get(true_synset, true_synset)\n\n    # Plot\n    plt.subplot(rows, cols, i + 1)\n    plt.imshow(img)\n    plt.axis(\"off\")\n    plt.title(f\"Pred: {predicted}\\nTrue: {true_label}\", fontsize=12)\n\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-16T06:36:27.397826Z","iopub.execute_input":"2025-09-16T06:36:27.398277Z","iopub.status.idle":"2025-09-16T06:36:33.029854Z","shell.execute_reply.started":"2025-09-16T06:36:27.398247Z","shell.execute_reply":"2025-09-16T06:36:33.028796Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"","metadata":{}},{"cell_type":"markdown","source":"## Looking on the filters of each layer \n\n","metadata":{}},{"cell_type":"code","source":"for name, p in model.named_parameters():\n    if name.endswith(\".weight\") and \"features\" in name:\n        print(name, tuple(p.shape))\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-16T06:36:33.030907Z","iopub.execute_input":"2025-09-16T06:36:33.031196Z","iopub.status.idle":"2025-09-16T06:36:33.036278Z","shell.execute_reply.started":"2025-09-16T06:36:33.031173Z","shell.execute_reply":"2025-09-16T06:36:33.035579Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model.features[0]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-16T06:36:33.036877Z","iopub.execute_input":"2025-09-16T06:36:33.037127Z","iopub.status.idle":"2025-09-16T06:36:33.055853Z","shell.execute_reply.started":"2025-09-16T06:36:33.037105Z","shell.execute_reply":"2025-09-16T06:36:33.055096Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model.features[0].weight.size()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-16T06:36:33.058285Z","iopub.execute_input":"2025-09-16T06:36:33.058509Z","iopub.status.idle":"2025-09-16T06:36:33.068774Z","shell.execute_reply.started":"2025-09-16T06:36:33.058491Z","shell.execute_reply":"2025-09-16T06:36:33.067894Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model.features[0].weight.shape   # (64, 3, 11, 11)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-16T06:36:33.070041Z","iopub.execute_input":"2025-09-16T06:36:33.070341Z","iopub.status.idle":"2025-09-16T06:36:33.085982Z","shell.execute_reply.started":"2025-09-16T06:36:33.070316Z","shell.execute_reply":"2025-09-16T06:36:33.085083Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model.features[0].out_channels   # 64","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-16T06:36:33.086905Z","iopub.execute_input":"2025-09-16T06:36:33.087158Z","iopub.status.idle":"2025-09-16T06:36:33.09565Z","shell.execute_reply.started":"2025-09-16T06:36:33.087134Z","shell.execute_reply":"2025-09-16T06:36:33.0948Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model.features[0].bias.shape","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-16T06:36:33.096545Z","iopub.execute_input":"2025-09-16T06:36:33.096787Z","iopub.status.idle":"2025-09-16T06:36:33.105423Z","shell.execute_reply.started":"2025-09-16T06:36:33.096769Z","shell.execute_reply":"2025-09-16T06:36:33.104728Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model.features[1]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-16T06:36:33.106364Z","iopub.execute_input":"2025-09-16T06:36:33.106607Z","iopub.status.idle":"2025-09-16T06:36:33.116123Z","shell.execute_reply.started":"2025-09-16T06:36:33.106586Z","shell.execute_reply":"2025-09-16T06:36:33.11532Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model.features[3].weight.size()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-16T06:36:33.116964Z","iopub.execute_input":"2025-09-16T06:36:33.117187Z","iopub.status.idle":"2025-09-16T06:36:33.128017Z","shell.execute_reply.started":"2025-09-16T06:36:33.11717Z","shell.execute_reply":"2025-09-16T06:36:33.127154Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model.features[6].weight.size()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-16T06:36:33.128891Z","iopub.execute_input":"2025-09-16T06:36:33.129128Z","iopub.status.idle":"2025-09-16T06:36:33.140968Z","shell.execute_reply.started":"2025-09-16T06:36:33.129109Z","shell.execute_reply":"2025-09-16T06:36:33.139967Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model.features[8].weight.size()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-16T06:36:33.142037Z","iopub.execute_input":"2025-09-16T06:36:33.142646Z","iopub.status.idle":"2025-09-16T06:36:33.157627Z","shell.execute_reply.started":"2025-09-16T06:36:33.142624Z","shell.execute_reply":"2025-09-16T06:36:33.156763Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model.features[10].weight.size()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-16T06:36:33.158582Z","iopub.execute_input":"2025-09-16T06:36:33.158858Z","iopub.status.idle":"2025-09-16T06:36:33.170787Z","shell.execute_reply.started":"2025-09-16T06:36:33.158827Z","shell.execute_reply":"2025-09-16T06:36:33.170107Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model.features[0].kernel_size         # -> (11, 11)\nmodel.features[3].kernel_size         # -> (5, 5)\nmodel.features[6].kernel_size         # -> (3, 3)\nmodel.features[8].kernel_size         # -> (3, 3)\nmodel.features[10].kernel_size        # -> (3, 3)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-16T06:36:33.171651Z","iopub.execute_input":"2025-09-16T06:36:33.172014Z","iopub.status.idle":"2025-09-16T06:36:33.187266Z","shell.execute_reply.started":"2025-09-16T06:36:33.171988Z","shell.execute_reply":"2025-09-16T06:36:33.186507Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"params_list = list(model.parameters())\nlen(params_list)      \n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-16T06:36:33.188209Z","iopub.execute_input":"2025-09-16T06:36:33.188844Z","iopub.status.idle":"2025-09-16T06:36:33.203346Z","shell.execute_reply.started":"2025-09-16T06:36:33.188816Z","shell.execute_reply":"2025-09-16T06:36:33.202516Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"total = sum(p.numel() for p in model.parameters())\ntrainable = sum(p.numel() for p in model.parameters() if p.requires_grad)\nprint(f\"Total: {total:,}  |  Trainable: {trainable:,}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-16T06:36:33.20406Z","iopub.execute_input":"2025-09-16T06:36:33.204292Z","iopub.status.idle":"2025-09-16T06:36:33.218376Z","shell.execute_reply.started":"2025-09-16T06:36:33.204273Z","shell.execute_reply":"2025-09-16T06:36:33.217668Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\nimport matplotlib.pyplot as plt\nfrom torchvision.models import alexnet, AlexNet_Weights\n\n# Load pretrained AlexNet\nweights = AlexNet_Weights.DEFAULT\nmodel = alexnet(weights=weights).eval()\n\n# Grab the first conv layer\nconv1 = model.features[0]\n\n# Extract weights: shape [64, 3, 11, 11]\nkernels = conv1.weight.data.clone()\n\n# Normalize each kernel for visualization\ndef normalize_kernel(k):\n    k = k - k.min()\n    k = k / k.max()\n    return k\n\n\n# Plot all 64 kernels\nfig, axes = plt.subplots(8, 8, figsize=(12, 12))\nfor i, ax in enumerate(axes.flat):\n    kernel = normalize_kernel(kernels[i])\n    # Move from [3,11,11] -> [11,11,3] for RGB plotting\n    kernel = kernel.permute(1, 2, 0).numpy()\n    ax.imshow(kernel)\n    ax.axis(\"off\")\nplt.suptitle(\"All 64 Conv1 Kernels (AlexNet)\")\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-16T06:36:33.219179Z","iopub.execute_input":"2025-09-16T06:36:33.219398Z","iopub.status.idle":"2025-09-16T06:36:35.4024Z","shell.execute_reply.started":"2025-09-16T06:36:33.219374Z","shell.execute_reply":"2025-09-16T06:36:35.401594Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"alexnet(weights=\"DEFAULT\") already has all five conv filter banks learned, and we can visualize them right away.\n\nHere’s a tiny, drop-in PyTorch snippet that makes three views:\n\n1. Conv1 filters as RGB tiles (the classic Gabor/color blobs)\n2. Conv2 filters projected to pixel space (compose Conv2 with Conv1 → ~11×11 RGB “what pattern in pixels this filter likes”)\n3. Channel-slice heatmaps for any deeper conv (shows how a single filter mixes many input channels)","metadata":{}},{"cell_type":"code","source":"import torch, torch.nn.functional as F\nfrom torchvision.utils import make_grid\nimport matplotlib.pyplot as plt\n\n# --- utils ---\ndef save_grid(chw_list, nrow, path, norm=True, scale_each=True):\n    tiles = []\n    for t in chw_list:\n        if t.ndim == 2: t = t[None, ...]      # (H,W) -> (1,H,W)\n        if t.shape[0] == 1: t = t.repeat(3,1,1)  # grayscale -> RGB for viewing\n        tiles.append(t.cpu())\n    grid = make_grid(torch.stack(tiles,0), nrow=nrow, normalize=norm, scale_each=scale_each, padding=2)\n    plt.figure(figsize=(12,12)); plt.axis('off'); plt.imshow(grid.permute(1,2,0))\n    plt.tight_layout(); plt.savefig(path, dpi=160, bbox_inches='tight'); plt.close()\n    print(\"saved:\", path)\n\ndef full_corr2d(a, b):  # “full” cross-correlation of 2D kernels (for composing kernels)\n    ph, pw = b.shape[-2]-1, b.shape[-1]-1\n    a = F.pad(a[None,None], (pw,pw,ph,ph), mode='constant', value=0)  # 1x1xH'×W'\n    b = b[None,None]\n    y = F.conv2d(a, b)  # 1x1x(Ha+Hb-1)x(Wa+Wb-1)\n    return y[0,0]\n\n# --- 1) Conv1 filters as RGB tiles ---\nW1 = model.features[0].weight.data.clone()          # (64, 3, 11, 11) in torchvision AlexNet\n# sort by L2 norm just to put chunky filters first\nidx1 = torch.topk(W1.view(W1.size(0), -1).norm(dim=1), k=W1.size(0)).indices\nsave_grid([W1[i] for i in idx1], nrow=8, path=\"conv1_rgb_filters.png\")\n\n# --- 2) Conv2 filters projected to pixel space via Conv1 (≈ 11×11 RGB) ---\nW2 = model.features[3].weight.data.clone()          # (192, 64, 5, 5)\nk_eff = W1.size(-1) + W2.size(-1) - 1               # 11 + 5 - 1 = 15 for AlexNet\nW2_px = torch.zeros(W2.size(0), 3, k_eff, k_eff)    # (192,3,15,15)\n\n# compose: for each Conv2 filter m and RGB channel c, sum over Conv1 ch k of (W1[k,c] (*) W2[m,k])\nwith torch.no_grad():\n    for m in range(W2.size(0)):\n        for c in range(3):\n            acc = torch.zeros(k_eff, k_eff)\n            for k in range(W1.size(0)):\n                acc += full_corr2d(W1[k, c], W2[m, k])\n            W2_px[m, c] = acc\n\nidx2 = torch.topk(W2.view(W2.size(0), -1).norm(dim=1), k=min(64, W2.size(0))).indices\nsave_grid([W2_px[i] for i in idx2], nrow=8, path=\"conv2_pixelspace_top64.png\")\n\n# --- 3) Channel-slice heatmaps for any deeper conv (e.g., Conv3) ---\ndef visualize_conv_channel_slices(conv_module, out_index=0, top_slices=64, fname=\"convX_filter_slices.png\"):\n    W = conv_module.weight.data.clone()             # (out_ch, in_ch, kH, kW)\n    m = int(torch.topk(W.view(W.size(0), -1).norm(dim=1), k=1).indices) if out_index is None else out_index\n    Wm = W[m]                                       # (in_ch, kH, kW)\n    # rank input-channel slices by energy\n    idx = torch.topk(Wm.view(Wm.size(0), -1).norm(dim=1), k=min(top_slices, Wm.size(0))).indices\n    save_grid([Wm[i] for i in idx], nrow=16, path=fname)\n\n# examples:\nvisualize_conv_channel_slices(model.features[6], out_index=None, top_slices=64, fname=\"conv3_filter_slices_top64.png\")\nvisualize_conv_channel_slices(model.features[8], out_index=None, top_slices=64, fname=\"conv4_filter_slices_top64.png\")\nvisualize_conv_channel_slices(model.features[10], out_index=None, top_slices=64, fname=\"conv5_filter_slices_top64.png\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-16T06:36:35.403209Z","iopub.execute_input":"2025-09-16T06:36:35.403412Z","iopub.status.idle":"2025-09-16T06:36:42.702831Z","shell.execute_reply.started":"2025-09-16T06:36:35.403397Z","shell.execute_reply":"2025-09-16T06:36:42.701942Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Filters Layer 1","metadata":{}},{"cell_type":"code","source":"from IPython.display import Image, display\ndisplay(Image(filename=\"/kaggle/working/conv1_rgb_filters.png\"))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-16T06:36:42.70368Z","iopub.execute_input":"2025-09-16T06:36:42.703936Z","iopub.status.idle":"2025-09-16T06:36:42.710967Z","shell.execute_reply.started":"2025-09-16T06:36:42.703917Z","shell.execute_reply":"2025-09-16T06:36:42.710047Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Filters Layer 2","metadata":{}},{"cell_type":"code","source":"display(Image(filename=\"/kaggle/working/conv2_pixelspace_top64.png\"))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-16T06:36:42.71422Z","iopub.execute_input":"2025-09-16T06:36:42.714446Z","iopub.status.idle":"2025-09-16T06:36:42.73417Z","shell.execute_reply.started":"2025-09-16T06:36:42.714428Z","shell.execute_reply":"2025-09-16T06:36:42.733302Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Filters Layer 3","metadata":{}},{"cell_type":"code","source":"display(Image(filename=\"/kaggle/working/conv3_filter_slices_top64.png\"))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-16T06:36:42.735046Z","iopub.execute_input":"2025-09-16T06:36:42.735315Z","iopub.status.idle":"2025-09-16T06:36:42.748622Z","shell.execute_reply.started":"2025-09-16T06:36:42.735296Z","shell.execute_reply":"2025-09-16T06:36:42.747957Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from PIL import Image, ImageDraw, ImageFont\nfrom IPython.display import Image as IPyImage, display\n\npaths = [\n    \"/kaggle/working/conv3_filter_slices_top64.png\",\n    \"/kaggle/working/conv4_filter_slices_top64.png\",\n    \"/kaggle/working/conv5_filter_slices_top64.png\",\n]\nlabels = [\"Conv3 slices\", \"Conv4 slices\", \"Conv5 slices\"]\n\n# ----- sizing & layout -----\ntarget_w = 900   # <- make wider/narrower here\ngutter   = 20    # vertical space between panels\nlabel_h  = 40    # label bar height\nside_pad = 20    # left/right padding\n\n# Pillow 10+ resampling compatibility\nResampling = getattr(Image, \"Resampling\", Image)\n\n# load & resize (keep aspect)\nimgs = [Image.open(p).convert(\"RGB\") for p in paths]\nresized = []\nfor im in imgs:\n    w, h = im.size\n    scale = target_w / max(1, w)\n    nh = int(round(h * scale))\n    resized.append(im.resize((target_w, nh), Resampling.BILINEAR))\n\n# canvas size\ntotal_w = target_w + side_pad * 2\ntotal_h = sum(im.size[1] for im in resized) + (label_h * len(resized)) + gutter * (len(resized) - 1)\ncanvas = Image.new(\"RGB\", (total_w, total_h), (255, 255, 255))\ndraw = ImageDraw.Draw(canvas)\n\n# font\ntry:\n    font = ImageFont.truetype(\"/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf\", 20)\nexcept Exception:\n    font = ImageFont.load_default()\n\ndef text_wh(draw_obj, text, font_obj):\n    if hasattr(draw_obj, \"textbbox\"):  # Pillow ≥10\n        l, t, r, b = draw_obj.textbbox((0, 0), text, font=font_obj)\n        return r - l, b - t\n    if hasattr(font_obj, \"getsize\"):   # fallback\n        return font_obj.getsize(text)\n    w = draw_obj.textlength(text, font=font_obj)\n    ascent, descent = font_obj.getmetrics()\n    return int(w), int(ascent + descent)\n\n# paste vertically with labels\ny = 0\nfor im, lab in zip(resized, labels):\n    # image\n    canvas.paste(im, (side_pad, y))\n    y += im.size[1]\n\n    # label centered under panel\n    tw, th = text_wh(draw, lab, font)\n    cx = side_pad + im.size[0] // 2\n    draw.text((cx - tw // 2, y + (label_h - th) // 2), lab, fill=(30, 30, 30), font=font)\n\n    y += label_h + gutter  # move down for next panel\n\nout_path = \"/kaggle/working/conv3to5_slices_stack.png\"\ncanvas.save(out_path, quality=95)\ndisplay(IPyImage(filename=out_path))\nprint(\"Saved:\", out_path)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-16T06:36:42.749541Z","iopub.execute_input":"2025-09-16T06:36:42.749797Z","iopub.status.idle":"2025-09-16T06:36:42.881511Z","shell.execute_reply.started":"2025-09-16T06:36:42.749775Z","shell.execute_reply":"2025-09-16T06:36:42.880766Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Next [Part3](https://www.kaggle.com/code/anako2020/cnn-deconvnet-part-3)","metadata":{}}]}