{"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":"gpu","dataSources":[{"sourceId":21755,"databundleVersionId":1475600,"sourceType":"competition"}],"dockerImageVersionId":31090,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# This Python 3 environment comes with many helpful analytics libraries installed\n# It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python\n# For example, here's several helpful packages to load\n\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n\n# Input data files are available in the read-only \"../input/\" directory\n# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory\n\nimport os\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    for filename in filenames:\n        print(os.path.join(dirname, filename))\n\n# You can write up to 20GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using \"Save & Run All\" \n# You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-07-16T09:03:08.952335Z","iopub.execute_input":"2025-07-16T09:03:08.952710Z","iopub.status.idle":"2025-07-16T09:03:11.430348Z","shell.execute_reply.started":"2025-07-16T09:03:08.952678Z","shell.execute_reply":"2025-07-16T09:03:11.429487Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"#  1 Setup Paths & Import Libraries","metadata":{}},{"cell_type":"code","source":"import os\nimport tensorflow as tf\nimport numpy as np\nimport matplotlib.pyplot as plt\nfrom glob import glob\nfrom PIL import Image\nfrom tqdm import tqdm","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-16T09:03:11.431860Z","iopub.execute_input":"2025-07-16T09:03:11.432516Z","iopub.status.idle":"2025-07-16T09:03:11.436341Z","shell.execute_reply.started":"2025-07-16T09:03:11.432490Z","shell.execute_reply":"2025-07-16T09:03:11.435561Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 2 Load Monet & Photo Image Paths","metadata":{}},{"cell_type":"code","source":"MONET_PATH = \"/kaggle/input/gan-getting-started/monet_jpg\"\nPHOTO_PATH = \"/kaggle/input/gan-getting-started/photo_jpg\"\n\nmonet_images = sorted(glob(os.path.join(MONET_PATH, \"*.jpg\")))\nphoto_images = sorted(glob(os.path.join(PHOTO_PATH, \"*.jpg\")))\n\nprint(f\"Monet: {len(monet_images)}, Photo: {len(photo_images)}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-16T09:03:11.439268Z","iopub.execute_input":"2025-07-16T09:03:11.439478Z","iopub.status.idle":"2025-07-16T09:03:11.465382Z","shell.execute_reply.started":"2025-07-16T09:03:11.439460Z","shell.execute_reply":"2025-07-16T09:03:11.464857Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 3 Preprocessing Function\nImages must be resized to 256x256 and normalized to [-1, 1] (for tanh activation).","metadata":{}},{"cell_type":"code","source":"def preprocess_image(image_path):\n    image = tf.io.read_file(image_path)\n    image = tf.image.decode_jpeg(image, channels=3)\n    image = tf.image.resize(image, [256, 256])\n    image = (tf.cast(image, tf.float32) / 127.5) - 1  # Normalize to [-1, 1]\n    return image","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-16T09:03:11.467284Z","iopub.execute_input":"2025-07-16T09:03:11.467714Z","iopub.status.idle":"2025-07-16T09:03:11.472069Z","shell.execute_reply.started":"2025-07-16T09:03:11.467696Z","shell.execute_reply":"2025-07-16T09:03:11.471285Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"#  4 TF Dataset Wrappers","metadata":{}},{"cell_type":"code","source":"import tensorflow as tf\nprint(\"GPUs:\", tf.config.list_physical_devices('GPU'))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-16T09:03:11.472683Z","iopub.execute_input":"2025-07-16T09:03:11.472927Z","iopub.status.idle":"2025-07-16T09:03:11.488205Z","shell.execute_reply.started":"2025-07-16T09:03:11.472910Z","shell.execute_reply":"2025-07-16T09:03:11.487602Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def load_dataset(image_paths, batch_size=1):\n    dataset = tf.data.Dataset.from_tensor_slices(image_paths)\n    dataset = dataset.map(preprocess_image, num_parallel_calls=tf.data.AUTOTUNE)\n    dataset = dataset.shuffle(1000).batch(batch_size).prefetch(tf.data.AUTOTUNE)\n    return dataset\n\nmonet_ds = load_dataset(monet_images)\nphoto_ds = load_dataset(photo_images)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-16T09:03:11.488946Z","iopub.execute_input":"2025-07-16T09:03:11.489213Z","iopub.status.idle":"2025-07-16T09:03:11.561060Z","shell.execute_reply.started":"2025-07-16T09:03:11.489190Z","shell.execute_reply":"2025-07-16T09:03:11.560372Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import matplotlib.pyplot as plt\n\n# Denormalize from [-1, 1] → [0, 1]\ndef denormalize(image):\n    return (image + 1.0) / 2.0\n\n# Plot real photo and Monet image pairs\ndef show_samples(photo_dataset, monet_dataset, num_images=5):\n    photo_iter = iter(photo_dataset)\n    monet_iter = iter(monet_dataset)\n\n    plt.figure(figsize=(12, num_images * 2.5))\n\n    for i in range(num_images):\n        photo_batch = next(photo_iter)   # shape: (1, 256, 256, 3)\n        monet_batch = next(monet_iter)\n\n        # Remove batch dimension → shape: (256, 256, 3)\n        photo = photo_batch[0]\n        monet = monet_batch[0]\n\n        photo = denormalize(photo)\n        monet = denormalize(monet)\n\n        # Show photo\n        ax = plt.subplot(num_images, 2, 2*i + 1)\n        plt.imshow(photo.numpy())\n        plt.title(\"Real Photo\")\n        plt.axis(\"off\")\n\n        # Show Monet painting\n        ax = plt.subplot(num_images, 2, 2*i + 2)\n        plt.imshow(monet.numpy())\n        plt.title(\"Real Monet Painting\")\n        plt.axis(\"off\")\n\n    plt.tight_layout()\n    plt.show()\n\n# ✅ Run it\nshow_samples(photo_ds, monet_ds, num_images=5)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-16T09:45:22.517930Z","iopub.execute_input":"2025-07-16T09:45:22.518202Z","iopub.status.idle":"2025-07-16T09:45:23.920258Z","shell.execute_reply.started":"2025-07-16T09:45:22.518184Z","shell.execute_reply":"2025-07-16T09:45:23.919524Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 5 Generator & Discriminator","metadata":{}},{"cell_type":"code","source":"!pip install git+https://github.com/tensorflow/examples.git","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-16T09:03:11.561757Z","iopub.execute_input":"2025-07-16T09:03:11.561974Z","iopub.status.idle":"2025-07-16T09:03:18.750958Z","shell.execute_reply.started":"2025-07-16T09:03:11.561954Z","shell.execute_reply":"2025-07-16T09:03:18.750198Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from tensorflow_examples.models.pix2pix import pix2pix","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-16T09:03:18.752165Z","iopub.execute_input":"2025-07-16T09:03:18.752390Z","iopub.status.idle":"2025-07-16T09:03:18.756879Z","shell.execute_reply.started":"2025-07-16T09:03:18.752369Z","shell.execute_reply":"2025-07-16T09:03:18.756032Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 6 Define CycleGAN Loss Functions","metadata":{}},{"cell_type":"code","source":"import tensorflow as tf\nfrom tensorflow.keras import layers","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-16T09:03:18.757635Z","iopub.execute_input":"2025-07-16T09:03:18.757841Z","iopub.status.idle":"2025-07-16T09:03:18.791008Z","shell.execute_reply.started":"2025-07-16T09:03:18.757818Z","shell.execute_reply":"2025-07-16T09:03:18.790286Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Binary cross entropy loss for GAN\nloss_obj = tf.keras.losses.BinaryCrossentropy(from_logits=True)\n\n# Discriminator loss: real images should be 1, fake should be 0\ndef discriminator_loss(real, generated):\n    real_loss = loss_obj(tf.ones_like(real), real)\n    generated_loss = loss_obj(tf.zeros_like(generated), generated)\n    return (real_loss + generated_loss) * 0.5\n\n# Generator adversarial loss: want to fool discriminator\ndef generator_loss(generated):\n    return loss_obj(tf.ones_like(generated), generated)\n\n# L1 cycle-consistency loss (real → fake → real)\nLAMBDA = 10\n\ndef calc_cycle_loss(real_image, cycled_image):\n    return tf.reduce_mean(tf.abs(real_image - cycled_image)) * LAMBDA\n\n# Identity loss (real Monet → Monet), to preserve color/style\ndef identity_loss(real_image, same_image):\n    return tf.reduce_mean(tf.abs(real_image - same_image)) * LAMBDA * 0.5","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-16T09:03:18.792225Z","iopub.execute_input":"2025-07-16T09:03:18.792451Z","iopub.status.idle":"2025-07-16T09:03:18.808275Z","shell.execute_reply.started":"2025-07-16T09:03:18.792435Z","shell.execute_reply":"2025-07-16T09:03:18.807707Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 7 CycleGAN Training Class","metadata":{}},{"cell_type":"code","source":"class CycleGAN(tf.keras.Model):\n    def __init__(self, generator_G, generator_F, discriminator_X, discriminator_Y):\n        super(CycleGAN, self).__init__()\n        self.gen_G = generator_G  # Photo → Monet\n        self.gen_F = generator_F  # Monet → Photo\n        self.disc_X = discriminator_X  # Monet discriminator\n        self.disc_Y = discriminator_Y  # Photo discriminator\n\n    def compile(self, gen_G_optimizer, gen_F_optimizer, disc_X_optimizer, disc_Y_optimizer,\n                gen_loss_fn, disc_loss_fn, cycle_loss_fn, identity_loss_fn):\n        super(CycleGAN, self).compile()\n        self.gen_G_optimizer = gen_G_optimizer\n        self.gen_F_optimizer = gen_F_optimizer\n        self.disc_X_optimizer = disc_X_optimizer\n        self.disc_Y_optimizer = disc_Y_optimizer\n        self.gen_loss_fn = gen_loss_fn\n        self.disc_loss_fn = disc_loss_fn\n        self.cycle_loss_fn = cycle_loss_fn\n        self.identity_loss_fn = identity_loss_fn\n\n    def train_step(self, batch_data):\n        real_x, real_y = batch_data  # x = photo, y = monet\n\n        with tf.GradientTape(persistent=True) as tape:\n            # Generate fakes\n            fake_y = self.gen_G(real_x, training=True)  # photo → fake Monet\n            fake_x = self.gen_F(real_y, training=True)  # Monet → fake photo\n\n            # Cycle back\n            cycled_x = self.gen_F(fake_y, training=True)\n            cycled_y = self.gen_G(fake_x, training=True)\n\n            # Identity mapping\n            same_x = self.gen_F(real_x, training=True)\n            same_y = self.gen_G(real_y, training=True)\n\n            # Discriminator predictions\n            disc_real_x = self.disc_X(real_y, training=True)\n            disc_real_y = self.disc_Y(real_x, training=True)\n\n            disc_fake_x = self.disc_X(fake_y, training=True)\n            disc_fake_y = self.disc_Y(fake_x, training=True)\n\n            # Losses\n            gen_G_loss = self.gen_loss_fn(disc_fake_x)\n            gen_F_loss = self.gen_loss_fn(disc_fake_y)\n\n            total_cycle_loss = self.cycle_loss_fn(real_x, cycled_x) + self.cycle_loss_fn(real_y, cycled_y)\n\n            id_loss_G = self.identity_loss_fn(real_y, same_y)\n            id_loss_F = self.identity_loss_fn(real_x, same_x)\n\n            total_gen_G_loss = gen_G_loss + total_cycle_loss + id_loss_G\n            total_gen_F_loss = gen_F_loss + total_cycle_loss + id_loss_F\n\n            disc_X_loss = self.disc_loss_fn(disc_real_x, disc_fake_x)\n            disc_Y_loss = self.disc_loss_fn(disc_real_y, disc_fake_y)\n\n        # Apply gradients\n        grads_G = tape.gradient(total_gen_G_loss, self.gen_G.trainable_variables)\n        grads_F = tape.gradient(total_gen_F_loss, self.gen_F.trainable_variables)\n\n        grads_disc_X = tape.gradient(disc_X_loss, self.disc_X.trainable_variables)\n        grads_disc_Y = tape.gradient(disc_Y_loss, self.disc_Y.trainable_variables)\n\n        self.gen_G_optimizer.apply_gradients(zip(grads_G, self.gen_G.trainable_variables))\n        self.gen_F_optimizer.apply_gradients(zip(grads_F, self.gen_F.trainable_variables))\n\n        self.disc_X_optimizer.apply_gradients(zip(grads_disc_X, self.disc_X.trainable_variables))\n        self.disc_Y_optimizer.apply_gradients(zip(grads_disc_Y, self.disc_Y.trainable_variables))\n\n        return {\n            \"G_loss\": total_gen_G_loss,\n            \"F_loss\": total_gen_F_loss,\n            \"D_X_loss\": disc_X_loss,\n            \"D_Y_loss\": disc_Y_loss,\n        }","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-16T09:03:18.809362Z","iopub.execute_input":"2025-07-16T09:03:18.809600Z","iopub.status.idle":"2025-07-16T09:03:18.827283Z","shell.execute_reply.started":"2025-07-16T09:03:18.809585Z","shell.execute_reply":"2025-07-16T09:03:18.826681Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 8 Instantiate Model","metadata":{}},{"cell_type":"code","source":"def build_discriminator():\n    initializer = tf.random_normal_initializer(0., 0.02)\n\n    inp = tf.keras.layers.Input(shape=[256, 256, 3], name='input_image')\n\n    x = tf.keras.layers.Conv2D(64, 4, strides=2, padding='same',\n                               kernel_initializer=initializer)(inp)\n    x = tf.keras.layers.LeakyReLU()(x)\n\n    x = tf.keras.layers.Conv2D(128, 4, strides=2, padding='same',\n                               kernel_initializer=initializer)(x)\n    x = tf.keras.layers.BatchNormalization()(x)\n    x = tf.keras.layers.LeakyReLU()(x)\n\n    x = tf.keras.layers.Conv2D(256, 4, strides=2, padding='same',\n                               kernel_initializer=initializer)(x)\n    x = tf.keras.layers.BatchNormalization()(x)\n    x = tf.keras.layers.LeakyReLU()(x)\n\n    x = tf.keras.layers.Conv2D(512, 4, strides=1, padding='same',\n                               kernel_initializer=initializer)(x)\n    x = tf.keras.layers.BatchNormalization()(x)\n    x = tf.keras.layers.LeakyReLU()(x)\n\n    x = tf.keras.layers.Conv2D(1, 4, strides=1, padding='same',\n                               kernel_initializer=initializer)(x)\n\n    return tf.keras.Model(inputs=inp, outputs=x)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-16T09:03:18.827956Z","iopub.execute_input":"2025-07-16T09:03:18.828170Z","iopub.status.idle":"2025-07-16T09:03:18.841224Z","shell.execute_reply.started":"2025-07-16T09:03:18.828155Z","shell.execute_reply":"2025-07-16T09:03:18.840381Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Photo → Monet generator\ngenerator_G = pix2pix.unet_generator(output_channels=3, norm_type='instancenorm')  # G: Photo → Monet\n\n# Monet → Photo generator\ngenerator_F = pix2pix.unet_generator(output_channels=3, norm_type='instancenorm')  # F: Monet → Photo\n\ndiscriminator_X = build_discriminator()  # For Monet\ndiscriminator_Y = build_discriminator()  # For Photo","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-16T09:03:18.843703Z","iopub.execute_input":"2025-07-16T09:03:18.843951Z","iopub.status.idle":"2025-07-16T09:03:19.592399Z","shell.execute_reply.started":"2025-07-16T09:03:18.843933Z","shell.execute_reply":"2025-07-16T09:03:19.591616Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"cyclegan_model = CycleGAN(generator_G, generator_F, discriminator_X, discriminator_Y)\n\ncyclegan_model.compile(\n    gen_G_optimizer=tf.keras.optimizers.Adam(2e-4, beta_1=0.5),\n    gen_F_optimizer=tf.keras.optimizers.Adam(2e-4, beta_1=0.5),\n    disc_X_optimizer=tf.keras.optimizers.Adam(2e-4, beta_1=0.5),\n    disc_Y_optimizer=tf.keras.optimizers.Adam(2e-4, beta_1=0.5),\n    gen_loss_fn=generator_loss,\n    disc_loss_fn=discriminator_loss,\n    cycle_loss_fn=calc_cycle_loss,\n    identity_loss_fn=identity_loss\n)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-16T09:03:19.593306Z","iopub.execute_input":"2025-07-16T09:03:19.593582Z","iopub.status.idle":"2025-07-16T09:03:19.612288Z","shell.execute_reply.started":"2025-07-16T09:03:19.593560Z","shell.execute_reply":"2025-07-16T09:03:19.611496Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_dataset = tf.data.Dataset.zip((photo_ds, monet_ds))\ncyclegan_model.fit(train_dataset, epochs=50)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-16T09:03:19.612990Z","iopub.execute_input":"2025-07-16T09:03:19.613182Z","iopub.status.idle":"2025-07-16T09:42:27.525564Z","shell.execute_reply.started":"2025-07-16T09:03:19.613167Z","shell.execute_reply":"2025-07-16T09:42:27.524887Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import matplotlib.pyplot as plt\n\n# Denormalize from [-1, 1] to [0, 1]\ndef denormalize(image):\n    return (image + 1.0) / 2.0\n\n# Generate Monet-style image (no expand_dims!)\ndef generate_monet(generator_G, photo_batch):\n    # The dataset gives us (1, 256, 256, 3)\n    generated_image = generator_G(photo_batch, training=False)[0]\n    return generated_image\n\n# Visualize Real Photo → Monet-style output\ndef show_generated_images(generator_G, photo_dataset, num_images=5):\n    photo_iter = iter(photo_dataset)\n\n    plt.figure(figsize=(10, num_images * 2.5))\n    \n    for i in range(num_images):\n        photo_batch = next(photo_iter)  # already shape (1, 256, 256, 3)\n        photo = photo_batch[0]  # remove batch dimension\n        generated = generate_monet(generator_G, photo_batch)\n\n        photo = denormalize(photo)\n        generated = denormalize(generated)\n\n        # Original Photo\n        ax = plt.subplot(num_images, 2, 2*i + 1)\n        plt.imshow(photo.numpy())\n        plt.title(\"Real Photo\")\n        plt.axis(\"off\")\n\n        # Monet-Style\n        ax = plt.subplot(num_images, 2, 2*i + 2)\n        plt.imshow(generated.numpy())\n        plt.title(\"Generated Monet-style\")\n        plt.axis(\"off\")\n\n    plt.tight_layout()\n    plt.show()\n\n# 🔁 Run it!\nshow_generated_images(generator_G, photo_ds, num_images=5)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-16T09:43:59.686857Z","iopub.execute_input":"2025-07-16T09:43:59.687139Z","iopub.status.idle":"2025-07-16T09:44:02.161531Z","shell.execute_reply.started":"2025-07-16T09:43:59.687119Z","shell.execute_reply":"2025-07-16T09:44:02.160721Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"#  9 Full Code to Generate Monet-Style Images","metadata":{}},{"cell_type":"code","source":"import os\nimport tensorflow as tf\nfrom PIL import Image\nfrom tqdm import tqdm\n\n# 1. Create output directory\noutput_dir = \"/kaggle/working/images\"\nos.makedirs(output_dir, exist_ok=True)\n\n# 2. Denormalize from [-1, 1] → [0, 255] for saving as JPG\ndef denormalize_to_uint8(image):\n    image = (image + 1.0) / 2.0  # scale to [0, 1]\n    image = tf.clip_by_value(image, 0.0, 1.0)\n    image = tf.image.convert_image_dtype(image, dtype=tf.uint8, saturate=True)\n    return image\n\n# 3. Generate Monet-style images\ndef generate_monet_images(generator_G, photo_dataset, num_images=7000):\n    count = 0\n    photo_iter = iter(photo_dataset)\n\n    for photo_batch in tqdm(photo_iter, total=num_images):\n        if count >= num_images:\n            break\n\n        # Run inference\n        generated = generator_G(photo_batch, training=False)[0]  # shape: (256, 256, 3)\n        generated = denormalize_to_uint8(generated)\n\n        # Convert to PIL Image and save\n        image = Image.fromarray(generated.numpy())\n        image.save(os.path.join(output_dir, f\"{count}.jpg\"))\n\n        count += 1\n\n# 4. Run generation\ngenerate_monet_images(generator_G, photo_ds.repeat(), num_images=7000)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-16T09:46:23.606988Z","iopub.execute_input":"2025-07-16T09:46:23.607587Z","iopub.status.idle":"2025-07-16T09:52:05.583485Z","shell.execute_reply.started":"2025-07-16T09:46:23.607565Z","shell.execute_reply":"2025-07-16T09:52:05.582767Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import matplotlib.pyplot as plt\nfrom PIL import Image\nimport os\n\n# Directory where generated images were saved in Phase 3\ngenerated_dir = \"/kaggle/working/images\"\n\n# Visualize N saved Monet-style images\ndef show_generated_images_from_disk(directory, num_images=5):\n    image_files = sorted(os.listdir(directory))[:num_images]\n\n    plt.figure(figsize=(10, 2 * num_images))\n\n    for i, img_file in enumerate(image_files):\n        image_path = os.path.join(directory, img_file)\n        img = Image.open(image_path)\n\n        ax = plt.subplot(num_images, 1, i + 1)\n        plt.imshow(img)\n        plt.title(f\"Generated Monet-style Image: {img_file}\")\n        plt.axis(\"off\")\n\n    plt.tight_layout()\n    plt.show()\n\n# 🔁 Run it\nshow_generated_images_from_disk(generated_dir, num_images=5)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-16T09:52:32.535474Z","iopub.execute_input":"2025-07-16T09:52:32.535737Z","iopub.status.idle":"2025-07-16T09:52:32.869039Z","shell.execute_reply.started":"2025-07-16T09:52:32.535720Z","shell.execute_reply":"2025-07-16T09:52:32.868324Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 10 Export to images.zip for Submission","metadata":{}},{"cell_type":"code","source":"import zipfile\nimport os\n\n# Paths\nimage_folder = \"/kaggle/working/images\"\nzip_path = \"/kaggle/working/images.zip\"\n\n# Zip the images\ndef zip_images(folder_path, output_zip):\n    with zipfile.ZipFile(output_zip, 'w', zipfile.ZIP_DEFLATED) as zipf:\n        for filename in sorted(os.listdir(folder_path)):\n            if filename.endswith(\".jpg\"):\n                file_path = os.path.join(folder_path, filename)\n                zipf.write(file_path, arcname=filename)  # arcname removes the folder path\n\n    print(f\"✅ Zipped {len(os.listdir(folder_path))} images to {output_zip}\")\n\n# Run the function\nzip_images(image_folder, zip_path)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-16T09:53:22.955128Z","iopub.execute_input":"2025-07-16T09:53:22.955872Z","iopub.status.idle":"2025-07-16T09:53:26.098160Z","shell.execute_reply.started":"2025-07-16T09:53:22.955847Z","shell.execute_reply":"2025-07-16T09:53:26.094499Z"}},"outputs":[],"execution_count":null}]}