{ "cells": [ { "cell_type": "code", "execution_count": null, "id": "e570205c-8d15-409a-a80a-2deabe8093db", "metadata": {}, "outputs": [], "source": [ "from cil.optimisation.functions import LeastSquares, SVRGFunction, SGFunction\n", "from cil.framework import AcquisitionGeometry\n", "from cil.plugins.astra import ProjectionOperator\n", "from cil.plugins.astra import FBP\n", "from cil.optimisation.utilities import MetricsDiagnostics, RandomSampling\n", "from cil.optimisation.algorithms import FISTA, ISTA\n", "\n", "from ProxSkip import ProxSkip\n", "from utils import StoppingCriterionTime, BM3DFunction\n", "from skimage.metrics import peak_signal_noise_ratio as PSNR\n", "from skimage.metrics import structural_similarity as SSIM\n", "from xdesign import Foam, discrete_phantom\n", "\n", "import numpy as np\n", "import matplotlib.pyplot as plt\n", "plt.rcParams[\"image.cmap\"] = \"inferno\"" ] }, { "cell_type": "markdown", "id": "95eac1e1-ad82-4977-9576-1b8cc99f8d57", "metadata": {}, "source": [ "### PnP Variance Reduced ProxSkip on a simulated dataset" ] }, { "cell_type": "markdown", "id": "3e1dd614-7f15-4894-b13d-a1b32c82c969", "metadata": {}, "source": [ "In this notebook, we play the following game. We compare algorithms by **time-to-quality**: that is, we measure how quickly each method achieves high reconstruction quality—quantified by **PSNR** and **SSIM**—with respect to the ground truth. Here, the ground truth is available, and we impose a fixed computational budget. The winner is the algorithm that attains the highest PSNR/SSIM within this time limit." ] }, { "cell_type": "markdown", "id": "d25fd695-7af3-4992-bf6e-0ce6fe299870", "metadata": {}, "source": [ "### Generate Foam Phantom" ] }, { "cell_type": "code", "execution_count": null, "id": "7154574f-40e1-4b60-828b-ddc4b1be8d69", "metadata": {}, "outputs": [], "source": [ "N = 347 # image size\n", "density = 0.025 # attenuation density of the image\n", "np.random.seed(1234)\n", "gt = discrete_phantom(Foam(size_range=[0.075, 0.005], gap=2e-3, porosity=1.0), size=N - 10)\n", "gt = gt / np.max(gt) * density\n", "gt = np.pad(gt, 5)\n", "gt[gt < 0] = 0" ] }, { "cell_type": "markdown", "id": "7963e21e-24c7-46e0-9cda-3c9c6aaf9678", "metadata": {}, "source": [ "### Generate Acquisition Data with simulated noise" ] }, { "cell_type": "code", "execution_count": null, "id": "226ded3c-a540-49d0-b0a3-3c560de87c96", "metadata": {}, "outputs": [], "source": [ "num_angles = 400 \n", "\n", "angles = np.linspace(0, 180, num_angles, endpoint=False, dtype=np.float32)\n", "ag2D = AcquisitionGeometry.create_Parallel2D()\\\n", " .set_panel(num_pixels=N)\\\n", " .set_angles(angles=angles)\n", "ig2D = ag2D.get_ImageGeometry()\n", "x_cil = ig2D.allocate()\n", "x_cil.fill(gt)\n", "A = ProjectionOperator(ig2D, ag2D, device=\"cpu\")\n", "\n", "# noiseless sinogram\n", "noiseless = A.direct(x_cil)\n", "\n", "## simulated noise\n", "max_intensity = 2000\n", "expected_counts = max_intensity * np.exp(-noiseless.array)\n", "noisy_counts = np.random.poisson(expected_counts).astype(np.float32)\n", "noisy_counts[noisy_counts == 0] = 1 # deal with 0s\n", "y_np = -np.log(noisy_counts / max_intensity)\n", "noisy = ag2D.allocate()\n", "noisy.fill(y_np)" ] }, { "cell_type": "code", "execution_count": null, "id": "faa9bb7a-9454-4c6e-802e-80c991cc0da3", "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "import matplotlib.pyplot as plt\n", "\n", "fig, axs = plt.subplots(1, 3, figsize=(12, 10), constrained_layout=True)\n", "\n", "axs[0].imshow(gt)\n", "axs[0].set_title(\"Ground Truth\")\n", "\n", "axs[1].imshow(noiseless.array)\n", "axs[1].set_title(\"Noiseless Sinogram\")\n", "\n", "axs[2].imshow(noisy.array)\n", "axs[2].set_title(\"Noisy Sinogram\")\n", "\n", "plt.show()" ] }, { "cell_type": "markdown", "id": "b138b305-26db-4c0e-a40f-687cff75d883", "metadata": {}, "source": [ "### FBP reconstruction" ] }, { "cell_type": "code", "execution_count": null, "id": "78e70056-aea9-4780-89e2-52351f51b9ac", "metadata": {}, "outputs": [], "source": [ "fbp = FBP(ig2D, ag2D, device=\"cpu\")(noisy)\n", "fbp.array[fbp.array<0] = 0\n", "plt.imshow(fbp.array)" ] }, { "cell_type": "markdown", "id": "498ad7aa-540b-426b-8810-991a72312e5c", "metadata": {}, "source": [ "### Family of considered algorithms (ProxSkip template)\n", "\n", "**Parameters:** $\\gamma>0$, probability $p\\in(0,1]$, data subsets $N$\n", "**Initialize:** $x_0, h_0 \\in \\mathbb{R}^n$\n", "\n", "For $k=0,1,\\dots,K-1$:\n", "\n", "1. Compute $G_k$ (an unbiased estimator of $\\nabla f(x_k)$).\n", "2. $$\\hat x_{k+1} = x_k - \\gamma\\big(G_k(x_k) - h_k\\big).$$\n", "3. Sample $\\theta_k\\sim\\mathrm{Bernoulli}(p)$, $\\theta_k\\in{0,1}$.\n", "4. If $\\theta_k=1$:\n", " $$x_{k+1}=\\mathrm{prox}_{\\frac{\\gamma}{p}g}\\left(\\hat x_{k+1}-\\frac{\\gamma}{p}h_k\\right),$$\n", " else:\n", " $$x_{k+1}=\\hat x_{k+1}.$$\n", "5. Update:\n", " $$h_{k+1}=h_k+\\frac{p}{\\gamma}\\big(x_{k+1}-\\hat x_{k+1}\\big).$$\n", "\n", "---\n", "\n", "| $p=1$ | $0