text: import lib
  import numpy as np
  from skimage import metrics
  from scipy.optimize import minimize as sciminimize
  import matplotlib.pyplot as plt
  import os
  import sys
  
  # Default MAXITER
  MAXITER = 25
  
  # Default TOLERANCE
  TOLERANCE = 10e-3
  
  # Methods Dictionary
  methods = {
      "naive": {"phi": lambda _: 0, "dphi": lambda _: 0},
      "tikhonov": {"phi": lambda X: 0.5 * np.linalg.norm(X) ** 2, "dphi": lambda X: X},
      "tv": {"phi": lib.totvar, "dphi": lib.grad_totvar},
  }
  
  # Array of (kernel length, standard deviation)
  blurs = ((5, 0.5), (7, 1), (9, 1.3))
  
  # Function that implement gradient method
  def minimize(x0, f, df, maxiter, abs_stop) -> np.ndarray:
      # initialize first values
      x_last = x0
      k = 0
  
      # Stopping criteria
      while np.linalg.norm(df(x_last)) > abs_stop and k < maxiter:
          # The direction is given by the gradient of the last iteration
          grad = df(x_last)
  
          # Backtracking step
          step = lib.next_step(x_last, f, grad)
  
          if step == -1:
              raise Exception("CG minimize not converging")
  
          # calculate new x with 'step' as alpha and '-grad' as the direction
          x_last = x_last - step * grad
          k = k + 1
      return x_last
  
  
  # read image data from file
  def phase0(name):
      path = "img/" + name + ".png"
      img = plt.imread(path).astype(np.float64)
      return img
  
  
  """
      Blurs an image matrix with the given parameters then applies gaussian noise
      blur = (kernel length, standard deviation)
      noise_factor = float
  """
  
  
  def blur(img, blur, noise_factor=0.05):
      # Generate a blurring filter
      K = lib.psf_fft(lib.gaussian_kernel(*blur), blur[0], img.shape)
      # Generate noise
      noise = np.random.normal(size=img.shape) * noise_factor
      # Apply blur and noise
      b = lib.A(img, K) + noise
      PSNR = metrics.peak_signal_noise_ratio(img, b)
      MSE = metrics.mean_squared_error(img, b)
      return (b, PSNR, MSE, K)
  
  
  # Apply blur and noise then return (the new image, PSF, PSNR and MSE, K)
  def phase1(img):
      bs = []
      # Iterate for each blurs
      for blur_spec in blurs:
          bs.append(blur(img, blur_spec))
      return bs
  
  
  def phasen(true_image, blurred, l, phi_dphi, minimize, maxiter):
      b = blurred[0]
      method_fns = f_generator(l, phi_dphi[0], phi_dphi[1])
      f, df = method_fns(blurred[3], b)
      deblurred = minimize(np.zeros(b.shape), f, df, maxiter)
      PSNR = metrics.peak_signal_noise_ratio(true_image, deblurred)
      MSE = metrics.mean_squared_error(true_image, deblurred)
      return (deblurred, PSNR, MSE)
  
  
  def phasen_multi(true_image, lambdas, blurred_images, method="naive", minFun="scipy"):
      deblurred = []
      phi_dphi = (methods[method]["phi"], methods[method]["dphi"])
      minimizeFun = sci_minimize if minFun == "scipy" else our_minimize
  
      for l in lambdas:
          for blurred in blurred_images:
              deblurred.append(
                  phasen(true_image, blurred, l, phi_dphi, minimizeFun, MAXITER)
              )
      return deblurred
  
  
  """
      f_generator = f(l, regulating_term, regulating_term_grad) -> hardcode_f(K,b) -> (f,df)
  
      Generalization of the function to minimize and of its derivative
  
      l = lambda
      regulating_term = fn(X) -> matrix of shape X
      regulating_term_grad = fn(X) -> matrix of shape X
  """
  
  
  def f_generator(l, regulating_term, regulating_term_grad):
      def hardcode_f(K, b):
          def f(x):
              X = x.reshape(b.shape)
              #    |        same for every method        |     |   user choise  |
              res = 0.5 * (np.linalg.norm(lib.A(X, K) - b) ** 2) + l * regulating_term(X)
              return np.sum(res)
  
          def df(x):
              X = x.reshape(b.shape)
              #    | same for every method |     |     user choise     |
              res = lib.AT(lib.A(X, K) - b, K) + l * regulating_term_grad(X)
              newRes = np.reshape(res, b.size)
              return newRes
  
          return (f, df)
  
      return hardcode_f
  
  
  """
      our_minimize = (x0, f, df, maxiter) -> matrix
  """
  
  
  def our_minimize(x0, f, df, maxiter):
      return np.reshape(
          minimize(np.copy(x0).reshape(x0.size), f, df, maxiter, TOLERANCE), x0.shape
      )
  
  
  """
      sci_minimize = (x0, f, df, maxiter) -> matrix
  """
  
  
  def sci_minimize(x0, f, df, maxiter):
      return np.reshape(
          sciminimize(f, x0, method="CG", jac=df, options={"maxiter": maxiter}).x,
          x0.shape,
      )
  
  
  def show_plt(
      file, method, lambdas, original, blurred_images, deblurred, figW=14, figH=7
  ):
      len_l = len(lambdas)
      len_b = len(blurred_images)
  
      fig, axs = plt.subplots(
          len_l + 2, len_b, constrained_layout=True, figsize=(figW, figH)
      )
      fig.suptitle(f"Immagine = {file}, Metodo = {method}, Max. Iterazioni = {MAXITER}")
  
      for row, ax in enumerate(axs[:, :]):
          for col, tx in enumerate(ax[:]):
              if row == 0:
                  tx.set_title(f"Originale")
                  tx.imshow(original, cmap="gray", vmin=0, vmax=1)
              elif row == 1:
                  tx.set_title(
                      f"sigma={blurs[col][1]} , dim={blurs[col][0]}x{blurs[col][0]}"
                  )
                  tx.imshow(blurred_images[col][0], cmap="gray", vmin=0, vmax=1)
              else:
                  coord = (row - 2) * (len_b) + col
                  tx.set_title(f"lambda={lambdas[row-2]}")
                  tx.imshow(deblurred[coord][0], cmap="gray", vmin=0, vmax=1)
  
      plt.show()
  
  
  if __name__ == "__main__":
      print(
          "Esempio di esecuzione:\npython main.py [naive|tikhonov|tv] [our|scipy] (MAXITER) ( ... lambdas )"
      )
      args = sys.argv[1:]
      if len(args) == 0 or args[0] not in methods:
          print("Devi scegliere un methodo : naive | tikhonov | tv ")
          exit()
  
      method = args[0]
      minFun = "scipy"
  
      if len(args) > 1:
          minFun = args[1]
          if minFun != "scipy" and minFun != "our":
              print("La funzione di minimizzazione deve essere 'our' o 'scipy'!")
              exit()
  
      try:
          if len(args) > 2:
              MAXITER = int(args[2])
      except:
          print("MAXITER deve essere un numero intero!")
          exit()
  
      # Default lambdas
      lambdas = [0.02, 0.04, 0.8, 0.16]
      try:
          if len(args) > 3:
              lambdas = np.array(args[3:]).astype(np.float64)
      except:
          print("Le lambda devono essere numeri!")
          exit()
  
      # files = os.listdir('img/')
      files = ["1"]
      for file in files:
          # Read image from file
          original = phase0(file)
          # Execute phase1 and get blurred_images
          blurred_images = phase1(original)
          # Execute with a regularization choosen with method param
          deblurred = phasen_multi(original, lambdas, blurred_images, method, minFun)
  
          show_plt(file, method, lambdas, original, blurred_images, deblurred)
  