Repository: hi-paris/deepdespeckling (language: python)

Issue:
Minor modifications before main merge



Discussion / hints:

Fix patch:
diff --git a/.editorconfig b/.editorconfig
deleted file mode 100644
index d4a2c44..0000000
--- a/.editorconfig
+++ /dev/null
@@ -1,21 +0,0 @@
-# http://editorconfig.org
-
-root = true
-
-[*]
-indent_style = space
-indent_size = 4
-trim_trailing_whitespace = true
-insert_final_newline = true
-charset = utf-8
-end_of_line = lf
-
-[*.bat]
-indent_style = tab
-end_of_line = crlf
-
-[LICENSE]
-insert_final_newline = false
-
-[Makefile]
-indent_style = tab
diff --git a/.gitignore b/.gitignore
index 4c915d1..be1bd66 100644
--- a/.gitignore
+++ b/.gitignore
@@ -104,3 +104,6 @@ ENV/
 # IDE settings
 .vscode/
 .idea/
+.DS_Store
+img/.DS_Store
+img/entire/.DS_Store
diff --git a/.travis.yml b/.travis.yml
index 369e553..9679dd2 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -2,6 +2,7 @@
 
 language: python
 python:
+  - 3.9
   - 3.8
   - 3.7
   - 3.6
@@ -25,4 +26,4 @@ deploy:
   on:
     tags: true
     repo: ykemiche/deepdespeckling
-    python: 3.8
+    python: 3.9
diff --git a/AUTHORS.rst b/AUTHORS.rst
deleted file mode 100644
index 707f203..0000000
--- a/AUTHORS.rst
+++ /dev/null
@@ -1,13 +0,0 @@
-=======
-Credits
-=======
-
-Development Lead
-----------------
-
-* youcef kemiche <y.kemiche06@hotmail.com>
-
-Contributors
-------------
-
-None yet. Why not be the first?
diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst
deleted file mode 100644
index b7b9336..0000000
--- a/CONTRIBUTING.rst
+++ /dev/null
@@ -1,128 +0,0 @@
-.. highlight:: shell
-
-============
-Contributing
-============
-
-Contributions are welcome, and they are greatly appreciated! Every little bit
-helps, and credit will always be given.
-
-You can contribute in many ways:
-
-Types of Contributions
-----------------------
-
-Report Bugs
-~~~~~~~~~~~
-
-Report bugs at https://github.com/ykemiche/deepdespeckling/issues.
-
-If you are reporting a bug, please include:
-
-* Your operating system name and version.
-* Any details about your local setup that might be helpful in troubleshooting.
-* Detailed steps to reproduce the bug.
-
-Fix Bugs
-~~~~~~~~
-
-Look through the GitHub issues for bugs. Anything tagged with "bug" and "help
-wanted" is open to whoever wants to implement it.
-
-Implement Features
-~~~~~~~~~~~~~~~~~~
-
-Look through the GitHub issues for features. Anything tagged with "enhancement"
-and "help wanted" is open to whoever wants to implement it.
-
-Write Documentation
-~~~~~~~~~~~~~~~~~~~
-
-deepdespeckling could always use more documentation, whether as part of the
-official deepdespeckling docs, in docstrings, or even on the web in blog posts,
-articles, and such.
-
-Submit Feedback
-~~~~~~~~~~~~~~~
-
-The best way to send feedback is to file an issue at https://github.com/ykemiche/deepdespeckling/issues.
-
-If you are proposing a feature:
-
-* Explain in detail how it would work.
-* Keep the scope as narrow as possible, to make it easier to implement.
-* Remember that this is a volunteer-driven project, and that contributions
-  are welcome :)
-
-Get Started!
-------------
-
-Ready to contribute? Here's how to set up `deepdespeckling` for local development.
-
-1. Fork the `deepdespeckling` repo on GitHub.
-2. Clone your fork locally::
-
-    $ git clone git@github.com:your_name_here/deepdespeckling.git
-
-3. Install your local copy into a virtualenv. Assuming you have virtualenvwrapper installed, this is how you set up your fork for local development::
-
-    $ mkvirtualenv deepdespeckling
-    $ cd deepdespeckling/
-    $ python setup.py develop
-
-4. Create a branch for local development::
-
-    $ git checkout -b name-of-your-bugfix-or-feature
-
-   Now you can make your changes locally.
-
-5. When you're done making changes, check that your changes pass flake8 and the
-   tests, including testing other Python versions with tox::
-
-    $ flake8 deepdespeckling tests
-    $ python setup.py test or pytest
-    $ tox
-
-   To get flake8 and tox, just pip install them into your virtualenv.
-
-6. Commit your changes and push your branch to GitHub::
-
-    $ git add .
-    $ git commit -m "Your detailed description of your changes."
-    $ git push origin name-of-your-bugfix-or-feature
-
-7. Submit a pull request through the GitHub website.
-
-Pull Request Guidelines
------------------------
-
-Before you submit a pull request, check that it meets these guidelines:
-
-1. The pull request should include tests.
-2. If the pull request adds functionality, the docs should be updated. Put
-   your new functionality into a function with a docstring, and add the
-   feature to the list in README.rst.
-3. The pull request should work for Python 3.5, 3.6, 3.7 and 3.8, and for PyPy. Check
-   https://travis-ci.com/ykemiche/deepdespeckling/pull_requests
-   and make sure that the tests pass for all supported Python versions.
-
-Tips
-----
-
-To run a subset of tests::
-
-
-    $ python -m unittest tests.test_deepdespeckling
-
-Deploying
----------
-
-A reminder for the maintainers on how to deploy.
-Make sure all your changes are committed (including an entry in HISTORY.rst).
-Then run::
-
-$ bump2version patch # possible: major / minor / patch
-$ git push
-$ git push --tags
-
-Travis will then deploy to PyPI if tests pass.
diff --git a/HISTORY.rst b/HISTORY.rst
deleted file mode 100644
index 1d56d92..0000000
--- a/HISTORY.rst
+++ /dev/null
@@ -1,8 +0,0 @@
-=======
-History
-=======
-
-0.1.0 (2022-05-09)
-------------------
-
-* First release on PyPI.
diff --git a/MANIFEST.in b/MANIFEST.in
deleted file mode 100644
index 816b7a9..0000000
--- a/MANIFEST.in
+++ /dev/null
@@ -1,15 +0,0 @@
-include AUTHORS.rst
-include CONTRIBUTING.rst
-include HISTORY.rst
-include LICENSE
-include README.rst
-
-recursive-include tests *
-
-recursive-include deepdespeckling/merlin/test/saved_model *
-recursive-include deepdespeckling/merlin/train/saved_model *
-
-recursive-exclude * __pycache__
-recursive-exclude * *.py[co]
-
-recursive-include docs *.rst conf.py Makefile make.bat *.jpg *.png *.gif *.pth
diff --git a/Makefile b/Makefile
index 236d5ba..4ddecab 100644
--- a/Makefile
+++ b/Makefile
@@ -1,87 +1,10 @@
-.PHONY: clean clean-build clean-pyc clean-test coverage dist docs help install lint lint/flake8
-.DEFAULT_GOAL := help
-
-define BROWSER_PYSCRIPT
-import os, webbrowser, sys
-
-from urllib.request import pathname2url
-
-webbrowser.open("file://" + pathname2url(os.path.abspath(sys.argv[1])))
-endef
-export BROWSER_PYSCRIPT
-
-define PRINT_HELP_PYSCRIPT
-import re, sys
-
-for line in sys.stdin:
-	match = re.match(r'^([a-zA-Z_-]+):.*?## (.*)$$', line)
-	if match:
-		target, help = match.groups()
-		print("%-20s %s" % (target, help))
-endef
-export PRINT_HELP_PYSCRIPT
-
-BROWSER := python -c "$$BROWSER_PYSCRIPT"
-
-help:
-	@python -c "$$PRINT_HELP_PYSCRIPT" < $(MAKEFILE_LIST)
-
-clean: clean-build clean-pyc clean-test ## remove all build, test, coverage and Python artifacts
-
-clean-build: ## remove build artifacts
-	rm -fr build/
-	rm -fr dist/
-	rm -fr .eggs/
-	find . -name '*.egg-info' -exec rm -fr {} +
-	find . -name '*.egg' -exec rm -f {} +
-
-clean-pyc: ## remove Python file artifacts
-	find . -name '*.pyc' -exec rm -f {} +
-	find . -name '*.pyo' -exec rm -f {} +
-	find . -name '*~' -exec rm -f {} +
-	find . -name '__pycache__' -exec rm -fr {} +
-
-clean-test: ## remove test and coverage artifacts
-	rm -fr .tox/
-	rm -f .coverage
-	rm -fr htmlcov/
-	rm -fr .pytest_cache
-
-lint/flake8: ## check style with flake8
-	flake8 deepdespeckling tests
-
-lint: lint/flake8 ## check style
-
-test: ## run tests quickly with the default Python
-	python setup.py test
-
-test-all: ## run tests on every Python version with tox
-	tox
-
-coverage: ## check code coverage quickly with the default Python
-	coverage run --source deepdespeckling setup.py test
-	coverage report -m
-	coverage html
-	$(BROWSER) htmlcov/index.html
-
-docs: ## generate Sphinx HTML documentation, including API docs
-	rm -f docs/deepdespeckling.rst
-	rm -f docs/modules.rst
-	sphinx-apidoc -o docs/ deepdespeckling
-	$(MAKE) -C docs clean
-	$(MAKE) -C docs html
-	$(BROWSER) docs/_build/html/index.html
-
-servedocs: docs ## compile the docs watching for changes
-	watchmedo shell-command -p '*.rst' -c '$(MAKE) -C docs html' -R -D .
-
-release: dist ## package and upload a release
-	twine upload dist/*
-
-dist: clean ## builds source and wheel package
-	python setup.py sdist
-	python setup.py bdist_wheel
-	ls -l dist
-
-install: clean ## install the package to the active Python's site-packages
-	python setup.py install
+# this target runs checks on all files
+quality:
+	isort . -c
+	flake8
+	mypy
+	black --check .
+# this target runs checks on all files and potentially modifies some of them
+style:
+	isort .
+	black .
\ No newline at end of file
diff --git a/README.md b/README.md
index 867d49c..f4bb258 100644
--- a/README.md
+++ b/README.md
@@ -1,15 +1,16 @@
-# deepdespeckling Synthetic Aperture Radar (SAR) images with Pytorch
+# deepdespeckling 
+## Synthetic Aperture Radar (SAR) images despeckling with Pytorch
 
-Speckle fluctuations seriously limit the interpretability of synthetic aperture radar (SAR) images. This package provides despeckling methods that can highly improve the quality and interpretability of SAR images. Both Stripmap and Spotlight operations are handled by this package. 
+Speckle fluctuations seriously limit the interpretability of synthetic aperture radar (SAR) images. This package provides despeckling methods that are leveraging deep learning to highly improve the quality and interpretability of SAR images. Both Stripmap and Spotlight operations are handled by this package. 
  
-The package contains both test and train parts, wether you wish to despeckle a single pic (test) or use our model to build or improve your own.
+The package contains both inference and training parts, wether you wish to despeckle a set of SAR images or use our model to build or improve your own.
 
 To get a test function using Tensorflow's framework : https://gitlab.telecom-paris.fr/ring/MERLIN/-/blob/master/README.md
 
 [![PyPI version](https://badge.fury.io/py/deepdespeckling.svg)](https://badge.fury.io/py/deepdespeckling)
 [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
 
-# Installation
+## Installation
 
 Install deepdespeckling by running in the command prompt :
 
@@ -17,60 +18,99 @@ Install deepdespeckling by running in the command prompt :
 pip install deepdespeckling
 ```
 
-# Authors
+## Authors
 
+* [Emanuele Dalsasso](https://emanueledalsasso.github.io/) (Researcher at ECEO, EPFL)
+* [Hadrien Mariaccia](https://www.linkedin.com/in/hadrien-mar/) (Hi! PARIS Machine Learning Research Engineer)
 
-* [Emanuele Dalsasso](https://perso.telecom-paristech.fr/dalsasso/) (Researcher at Telecom Paris)
-* [Youcef Kemiche](https://www.linkedin.com/in/youcef-kemiche-3095b9174/) (Hi! PARIS Machine Learning Research Engineer)
-* [Pierre Blanchard](https://www.linkedin.com/in/pierre-blanchard-28245462/) (Hi! PARIS Engineer)
+## Former contributors 
 
+* [Youcef Kemiche](https://www.linkedin.com/in/youcef-kemiche-3095b9174/) (Former Hi! PARIS Machine Learning Research Engineer)
+* [Pierre Blanchard](https://www.linkedin.com/in/pierre-blanchard-28245462/) (Former Hi! PARIS Engineer)
 
-# Use cases
 
-### Test
-The package offers you 3 different methods for despeckling your SAR images: the fullsize method, the coordinates based method and the crop method.
+## Examples
 
-1) I have a high-resolution SAR image and I want to apply the despeckling function to the whole of it:
+Deepdespeckling offers 2 differents methods to despeckle SAR images: 
+
+* [MERLIN] (https://arxiv.org/pdf/2110.13148.pdf)
+* [SAR2SAR] (https://arxiv.org/pdf/2006.15037.pdf)
+
+### Despeckle one image
 
 ```python
-from deepdespeckling.merlin.test.spotlight import despeckle_spotlight
+from deepdespeckling.utils.load_cosar import cos2mat
+from deepdespeckling.utils.constants import PATCH_SIZE, STRIDE_SIZE
+from deepdespeckling.despeckling import get_model_weights_path, get_denoiser
 
+# Path to one image (cos, tiff or npy file, depending on the model you want to use), can also be a folder of several images
 image_path="path/to/cosar/image"
+# Model name, can be "spotlight", "stripmap" or "sar2sar"
+model_name = "spotlight"
+
+image = cos2mat(image_path).astype(np.float32)
+# Get the right model
+denoiser = get_denoiser(model_name=model_name)
+model_weights_path = get_model_weights_path(model_name=model_name)
+
+denoised_image = denoiser.denoise_image(
+                image, model_weights_path, patch_size=PATCH_SIZE, stride_size=STRIDE_SIZE)
+```
+
+### Despeckle a set of images
+
+For each of this method, you can choose between 3 different functions to despeckle a set of SAR images contained in a folder : 
+
+* `despeckle()` to despeckle full size images
+* `despeckle_from_coordinates()` to despeckle a sub-part of the images defined by some coordinates
+* `despeckle_from_crop()` to despeckle a sub-part of the images defined using a crop tool
+
+#### Despeckle fullsize images
+
+```python
+from deepdespeckling.despeckling import despeckle
+
+# Path to one image (cos or npy file), can also be a folder of several images
+image_path="path/to/cosar/image"
+# Folder where results are stored
 destination_directory="path/where/to/save/results"
-model_weights_path="path/to/model/weights"
 
-despeckle_spotlight(image_path,destination_directory,model_weights_path=model_weights_path)
+denoised_image = despeckle(image_path, destination_directory, model_name="spotlight")
 ```
 Noisy image             |  Denoised image
 :----------------------:|:-------------------------:
 ![](img/entire/noisy.png)  |  ![](img/entire/denoised.png)
 
-2) I have a high-resolution SAR image but I only want to apply the despeckling function to a specific area for which I know the coordinates:
+#### Despeckle parts of images using custom coordinates
+
 ```python
-from deepdespeckling.merlin.test.spotlight import despeckle_from_coordinates_spotlight
+from deepdespeckling.despeckling import despeckle_from_coordinates
 
+# Path to one image (cos or npy file), can also be a folder of several images
 image_path="path/to/cosar/image"
+# Folder where results are stored
 destination_directory="path/where/to/save/results"
-model_weights_path="path/to/model/weights"
 coordinates_dictionnary = {'x_start':2600,'y_start':1000,'x_end':3000,'y_end':1200}
 
-despeckle_from_coordinates_spotlight(image_path, coordinates_dict, destination_directory, model_weights_path)
-````
+denoised_image = despeckle_from_coordinates(image_path, coordinates_dict, destination_directory, model_name="spotlight")
+```
 
 Noisy image             |  Denoised image
 :----------------------:|:-------------------------:
 ![](img/coordinates/noisy_test_image_data.png)  |  ![](img/coordinates/denoised_test_image_data.png)
 
-3) I have a high-resolution SAR image but I want to apply the despeckling function to an area I want to select with a crop:
+#### Despeckle parts of images using a crop tool
+
 ```python
-from deepdespeckling.merlin.test.spotlight import despeckle_from_crop_spotlight
+from deepdespeckling.merlin.inference.despeckling import despeckle_from_crop
 
+# Path to one image (cos or npy file), can also be a folder of several images
 image_path="path/to/cosar/image"
+# Folder where results are stored
 destination_directory="path/where/to/save/results"
-model_weights_path="path/to/model/weights"
 fixed = True "(it will crop a 256*256 image from the position of your click)" or False "(you will draw free-handly the area of your interest)"
 
-despeckle_from_crop_spotlight(image_path, destination_directory, model_weights_path, fixed=False)
+denoised_image = despeckle_from_crop(image_path, destination_directory, model_name="spotlight", fixed=False)
 ```
 
 * The cropping tool: Just select an area and press "q" when you are satisfied with the crop !
@@ -85,15 +125,12 @@ Noisy cropped image                     |           Denoised cropped image
 :-----------------------------------------------------------:|:------------------------------------------:
  <img src="img/crop/noisy_test_image_data.png" width="100%"> | <img src="img/crop/denoised_test_image_data.png" width="1000%">
 
-you can use the same features for stripmap images by importing : 
-```python
-from deepdespeckling.merlin.test.stripmap import despeckle_from_crop_stripmap,despeckle_stripmap,despeckle_from_coordinates_stripmap
-```
-### Train
+
+### Train a new model
 
 1) I want to train my own model from scratch:
 ```python
-from deepdespeckling.merlin.train import create_model, fit_model
+from deepdespeckling.merlin.training.train import create_model, fit_model
 nb_epoch=1
 
 # schedule the learning rate
@@ -109,14 +146,14 @@ sample_directory="path/to/sample/data"
 from_pretrained=False
 
 model=create_model(batch_size=12,val_batch_size=1,device=torch.device("cuda:0" if torch.cuda.is_available() else "cpu"),from_pretrained=from_pretrained)
-fit_model(model,lr,nb_epoch,training_set_directory,validation_set_directory,sample_directory,save_directory,seed=2)
+fit_model(model, lr, nb_epoch, training_set_directory, validation_set_directory, sample_directory, save_directory, seed=2)
 
 ```
 
 2) I want to train a model using the pre-trained version :
 ```python
-from deepdespeckling.merlin.train import create_model, fit_model
-from merlinsar.train.model import Model
+from deepdespeckling.merlin.training.train import create_model, fit_model
+from deepdespeckling.merlin.training.model import Model
 
 nb_epoch=1
 
@@ -131,15 +168,10 @@ save_directory="path/where/to/save/results"
 sample_directory="path/to/sample/data"
 from_pretrained=True
 
-model=create_model(Model,batch_size=12,val_batch_size=1,device=torch.device("cuda:0" if torch.cuda.is_available() else "cpu"),from_pretrained=from_pretrained)
-fit_model(model,lr,nb_epoch,training_set_directory,validation_set_directory,sample_directory,save_directory,seed=2)
+model=create_model(Model, batch_size=12, val_batch_size=1, device=torch.device("cuda:0" if torch.cuda.is_available() else "cpu"), from_pretrained=from_pretrained)
+fit_model(model, lr, nb_epoch, training_set_directory, validation_set_directory, sample_directory, save_directory, seed=2)
 ```
 
-# Contribute
-
-
-- Source Code: https://github.com/hi-paris/deepdespeckling.git
-
 # License
 
 * Free software: MIT
@@ -151,3 +183,5 @@ fit_model(model,lr,nb_epoch,training_set_directory,validation_set_directory,samp
 # References
 
 [1] DALSASSO, Emanuele, DENIS, Loïc, et TUPIN, Florence. [As if by magic: self-supervised training of deep despeckling networks with MERLIN](https://arxiv.org/pdf/2110.13148.pdf). IEEE Transactions on Geoscience and Remote Sensing, 2021, vol. 60, p. 1-13.
+
+[2] DALSASSO, Emanuele, DENIS, Loïc, et TUPIN, Florence. [SAR2SAR: a semi-supervised despeckling algorithm for SAR images](https://arxiv.org/pdf/2006.15037.pdf). IEEE Journal of Selected Topics in Applied Earth Observations and Remote Sensing (Early Access), 2020
diff --git a/deepdespeckling/__init__.py b/deepdespeckling/__init__.py
deleted file mode 100644
index 305ecf0..0000000
--- a/deepdespeckling/__init__.py
+++ /dev/null
@@ -1,3 +0,0 @@
-"""Unit test package for merlin."""
-
-import deepdespeckling.merlin
diff --git a/deepdespeckling/denoiser.py b/deepdespeckling/denoiser.py
new file mode 100644
index 0000000..b17d543
--- /dev/null
+++ b/deepdespeckling/denoiser.py
@@ -0,0 +1,117 @@
+import logging
+import torch
+import numpy as np
+from tqdm import tqdm
+from pathlib import Path
+from glob import glob
+
+from deepdespeckling.model import Model
+from deepdespeckling.utils.constants import M, m
+from deepdespeckling.utils.utils import load_sar_image
+
+
+class Denoiser():
+    """Class to share parameters beyond denoising functions
+    """
+
+    def __init__(self):
+        self.device = self.get_device()
+
+    def get_device(self) -> str:
+        """Get torch device to use depending on gpu's availability
+
+        Returns:
+            device (str): device to be used by torch
+        """
+        if torch.backends.mps.is_available() and torch.backends.mps.is_built():
+            device = "mps"
+        elif torch.cuda.is_available():
+            device = "cuda:0"
+        else:
+            device = "cpu"
+        logging.info(f"{device} device is used by torch")
+
+        return device
+
+    def load_model(self, weights_path: str, patch_size: int) -> Model:
+        """Load model with given weights 
+
+        Args:
+            weights_path (str): path to weights  
+            patch_size (int): patch size
+
+        Returns:
+            model (Model): model loaded with stored weights
+        """
+        model = Model(torch.device(self.device),
+                      height=patch_size, width=patch_size)
+        model.load_state_dict(torch.load(
+            weights_path, map_location=torch.device("cpu")))
+
+        return model
+
+    def initialize_axis_range(self, image_axis_dim: int, patch_size: int, stride_size: int) -> list:
+        """Initialize the convolution range for x or y axis
+
+        Args:
+            image_axis_dim (int): axis size
+            patch_size (int): patch size
+            stride_size (int): stride size
+
+        Returns:
+            axis_range (list) : pixel borders of each convolution
+        """
+        if image_axis_dim == patch_size:
+            axis_range = list(np.array([0]))
+        else:
+            axis_range = list(
+                range(0, image_axis_dim - patch_size, stride_size))
+            if (axis_range[-1] + patch_size) < image_axis_dim:
+                axis_range.extend(
+                    range(image_axis_dim - patch_size, image_axis_dim - patch_size + 1))
+
+        return axis_range
+
+    def save_despeckled_images(self):
+        raise NotImplementedError
+
+    def denoise_image_kernel(self):
+        raise NotImplementedError
+
+    def preprocess_denoised_image(self):
+        raise NotImplementedError
+
+    def denoise_image(self):
+        raise NotImplementedError
+
+    def denoise_images(self, images_to_denoise_path: list, weights_path: str, save_dir: str, patch_size: int, stride_size: int):
+        """Iterate over a directory of coSAR images and store the denoised images in a directory
+
+        Args:
+            images_to_denoise_path (list): a list of paths of npy images to denoise
+            weights_path (str): path to the pth file containing the weights of the model
+            save_dir (str): repository to save sar images, real images and noisy images
+            patch_size (int): size of the patch of the convolution
+            stride_size (int): number of pixels between one convolution to the next
+        """
+
+        images_to_denoise_paths = glob((images_to_denoise_path + '/*.npy'))
+
+        assert len(images_to_denoise_paths) != 0, 'No data!'
+
+        logging.info(f"Starting denoising images in {images_to_denoise_paths}")
+
+        for idx in range(len(images_to_denoise_paths)):
+            image_name = Path(images_to_denoise_paths[idx]).name
+            logging.info(
+                f"Despeckling {image_name}")
+
+            noisy_image_idx = load_sar_image(
+                images_to_denoise_paths[idx]).astype(np.float32)
+            despeckled_images = self.denoise_image(
+                noisy_image_idx, weights_path, patch_size, stride_size)
+
+            logging.info(
+                f"Saving despeckled images in {save_dir}")
+            self.save_despeckled_images(
+                despeckled_images, image_name, save_dir)
diff --git a/deepdespeckling/despeckling.py b/deepdespeckling/despeckling.py
new file mode 100644
index 0000000..01f65b4
--- /dev/null
+++ b/deepdespeckling/despeckling.py
@@ -0,0 +1,167 @@
+import logging
+import os
+from glob import glob
+from deepdespeckling.denoiser import Denoiser
+
+from deepdespeckling.merlin.inference.merlin_denoiser import MerlinDenoiser
+from deepdespeckling.sar2sar.sar2sar_denoiser import Sar2SarDenoiser
+from deepdespeckling.utils.constants import PATCH_SIZE, STRIDE_SIZE
+from deepdespeckling.utils.utils import (crop_image, get_cropping_coordinates, load_sar_image, preprocess_and_store_sar_images_from_coordinates,
+                                         create_empty_folder_in_directory, preprocess_and_store_sar_images)
+
+
+this_dir, this_filename = os.path.split(__file__)
+logging.basicConfig(level=logging.INFO)
+
+
+def get_model_weights_path(model_name: str) -> str:
+    """Get model weights path from model name
+
+    Args:
+        model_name (str): model name, either "spotlight" or "stripmap" to select MERLIN model on the 
+            right cosar image format or "sar2sar" for SAR2SAR model
+
+    Returns:
+        model_weights_path (str): the path of the weights of the specified model
+    """
+    if model_name == "spotlight":
+        model_weights_path = os.path.join(
+            this_dir, "merlin/saved_model", "spotlight.pth")
+    elif model_name == "stripmap":
+        model_weights_path = os.path.join(
+            this_dir, "merlin/saved_model", "stripmap.pth")
+    elif model_name == "sar2sar":
+        model_weights_path = os.path.join(
+            this_dir, "sar2sar/saved_model", "sar2sar.pth")
+    else:
+        raise ValueError("The model name doesn't refer to an existing model ")
+
+    return model_weights_path
+
+
+def get_denoiser(model_name: str) -> Denoiser:
+    """Get the right denoiser object from the model name
+
+    Args:
+        model_name (str): model name to be use for despeckling 
+
+    Returns:
+        denoiser (Denoiser): the right denoiser, Sar2SarDenoiser or MerlinDenoiser
+    """
+    if model_name in ["spotlight", "stripmap"]:
+        denoiser = MerlinDenoiser()
+    elif model_name == "sar2sar":
+        denoiser = Sar2SarDenoiser()
+    else:
+        raise ValueError("The model name doesn't refer to an existing model ")
+
+    return denoiser
+
+
+def despeckle(sar_images_path: str, destination_directory_path: str, model_name: str = "spotlight", patch_size: int = PATCH_SIZE, stride_size: int = STRIDE_SIZE):
+    """Despeckle coSAR images using trained MERLIN (spotlight or stripmap weights) or SAR2SAR
+
+    Args:
+        sar_images_path (str): path of sar images
+        destination_directory_path (str): path of folder in which results will be stored
+        patch_size (int): patch size. Defaults to constant PATCH_SIZE.
+        stride_size (int): stride size. Defaults to constant STRIDE_SIZE.
+        model_name (str): model name, either "spotlight" or "stripmap" to select MERLIN model on the 
+            right cosar image format or "sar2sar" for SAR2SAR model. Default to "spotlight"
+    """
+
+    logging.info(
+        f"""Despeckling entire images using {model_name} weights""")
+
+    model_weights_path = get_model_weights_path(model_name=model_name)
+
+    processed_images_path = create_empty_folder_in_directory(destination_directory_path=destination_directory_path,
+                                                             folder_name="processed_images")
+    preprocess_and_store_sar_images(
+        sar_images_path=sar_images_path, processed_images_path=processed_images_path, model_name=model_name)
+
+    logging.info(
+        f"Starting inference.. Collecting data from {sar_images_path} and storing test results in {destination_directory_path}")
+
+    denoiser = get_denoiser(model_name=model_name)
+    denoiser.denoise_images(images_to_denoise_path=processed_images_path, weights_path=model_weights_path, save_dir=destination_directory_path,
+                            patch_size=patch_size, stride_size=stride_size)
+
+
+def despeckle_from_coordinates(sar_images_path: str, coordinates_dict: dict, destination_directory_path: str, model_name: str = "spotlight",
+                               patch_size: int = PATCH_SIZE, stride_size: int = STRIDE_SIZE):
+    """Despeckle specified area with coordinates in coSAR images using trained MERLIN (spotlight or stripmap weights)
+
+    Args:
+        sar_images_path (str): path of sar images
+        coordinates_dict (dict): dictionary containing pixel boundaries of the area to despeckle (x_start, x_end, y_start, y_end)
+        destination_directory_path (str): path of folder in which results will be stored
+        patch_size (int): patch size. Defaults to constant PATCH_SIZE.
+        stride_size (int): stride size. Defaults to constant STRIDE_SIZE.
+        model_name (str): model name, either "spotlight" or "stripmap" to select MERLIN model on the 
+            right cosar image format or "sar2sar" for SAR2SAR model. Default to "spotlight"
+    """
+
+    logging.info(
+        f"""Despeckling images from coordinates using {model_name} weights""")
+
+    model_weights_path = get_model_weights_path(model_name=model_name)
+
+    processed_images_path = create_empty_folder_in_directory(destination_directory_path=destination_directory_path,
+                                                             folder_name="processed_images")
+    preprocess_and_store_sar_images_from_coordinates(sar_images_path=sar_images_path, processed_images_path=processed_images_path,
+                                                     coordinates_dict=coordinates_dict, model_name=model_name)
+
+    logging.info(
+        f"Starting inference.. Collecting data from {sar_images_path} and storing test results in {destination_directory_path}")
+
+    denoiser = get_denoiser(model_name=model_name)
+    denoiser.denoise_images(images_to_denoise_path=processed_images_path, weights_path=model_weights_path, save_dir=destination_directory_path,
+                            patch_size=patch_size, stride_size=stride_size)
+
+
+def despeckle_from_crop(sar_images_path: str, destination_directory_path: str, model_name: str = "spotlight", patch_size: int = PATCH_SIZE,
+                        stride_size: int = STRIDE_SIZE, fixed: bool = True):
+    """Despeckle specified area with an integrated cropping tool (made with OpenCV) in coSAR images using trained MERLIN (spotlight or stripmap weights)
+
+    Args:
+        sar_images_path (str): path of sar images
+        destination_directory_path (str): path of folder in which results will be stored
+        patch_size (int): patch size. Defaults to constant PATCH_SIZE.
+        stride_size (int): stride size. Defaults to constant STRIDE_SIZE.
+        model_name (str): model name, either "spotlight" or "stripmap" to select MERLIN model on the 
+            right cosar image format or "sar2sar" for SAR2SAR model. Default to "spotlight"
+        fixed (bool) : If True, crop size is limited to 256*256. Defaults to True
+    """
+
+    logging.info(
+        f"""Cropping and despeckling images using {model_name} weights""")
+
+    model_weights_path = get_model_weights_path(model_name=model_name)
+
+    processed_images_path = create_empty_folder_in_directory(destination_directory_path=destination_directory_path,
+                                                             folder_name="processed_images")
+
+    ext = "cos" if model_name in ["spotlight", "stripmap"] else "tiff"
+    images_paths = glob(os.path.join(sar_images_path, f"*.{ext}")) + \
+        glob(os.path.join(sar_images_path, "*.npy"))
+
+    for i, image_path in enumerate(images_paths):
+        # Load image for cropping
+        image = load_sar_image(image_path)
+
+        # Get cropping coordinates from the first image of the list of images to crop and despeckle
+        if i == 0:
+            cropping_coordinates = get_cropping_coordinates(
+                image=image, fixed=fixed, destination_directory_path=destination_directory_path, model_name=model_name)
+
+        # Crop image using stored cropping coordinates and store it in processed_images_path
+        crop_image(image, image_path, cropping_coordinates, model_name,
+                   processed_images_path)
+
+    logging.info(
+        f"Starting inference.. Collecting data from {sar_images_path} and storing results in {destination_directory_path}")
+
+    denoiser = get_denoiser(model_name=model_name)
+    denoiser.denoise_images(images_to_denoise_path=processed_images_path, weights_path=model_weights_path, save_dir=destination_directory_path,
+                            patch_size=patch_size, stride_size=stride_size)
diff --git a/deepdespeckling/merlin/__init__.py b/deepdespeckling/merlin/__init__.py
index cec0676..e69de29 100644
--- a/deepdespeckling/merlin/__init__.py
+++ b/deepdespeckling/merlin/__init__.py
@@ -1,3 +0,0 @@
-"""Unit test package for merlin."""
-
-import deepdespeckling.merlin.train, deepdespeckling.merlin.test
diff --git a/deepdespeckling/merlin/inference/__init__.py b/deepdespeckling/merlin/inference/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/deepdespeckling/merlin/inference/merlin_denoiser.py b/deepdespeckling/merlin/inference/merlin_denoiser.py
new file mode 100644
index 0000000..55c704f
--- /dev/null
+++ b/deepdespeckling/merlin/inference/merlin_denoiser.py
@@ -0,0 +1,207 @@
+import logging
+import torch
+import numpy as np
+from tqdm import tqdm
+from pathlib import Path
+from glob import glob
+
+from deepdespeckling.denoiser import Denoiser
+from deepdespeckling.model import Model
+from deepdespeckling.utils.constants import M, m
+from deepdespeckling.utils.utils import (denormalize_sar_image, save_image_to_npy_and_png,
+                                         symetrise_real_and_imaginary_parts, create_empty_folder_in_directory)
+
+
+class MerlinDenoiser(Denoiser):
+    """Class to share parameters beyond denoising functions
+    """
+
+    def __init__(self, **params):
+        super().__init__(**params)
+
+    def save_despeckled_images(self, despeckled_images: dict, image_name: str, save_dir: str):
+        """Save full, real and imaginary part of noisy and denoised image stored in a dictionary in png to a given folder
+
+        Args:
+            despeckled_images (dict): dictionary containing full, real and imaginary parts of noisy and denoised image
+            image_name (str): name of the image
+            save_dir (str): path to the folder where to save the png images
+        """
+        threshold = np.mean(
+            despeckled_images["noisy"]["full"]) + 3 * np.std(despeckled_images["noisy"]["full"])
+        image_name = image_name.split('\\')[-1]
+
+        for key in despeckled_images:
+            create_empty_folder_in_directory(save_dir, key)
+            for key2 in despeckled_images[key]:
+                save_image_to_npy_and_png(
+                    despeckled_images[key][key2], save_dir, f"/{key}/{key}_{key2}_", image_name, threshold)
+
+    def symetrise_real_and_imaginary_kernel(self, x: int, y: int, i_real_part: np.array, i_imag_part: np.array, patch_size: int) -> (torch.tensor, torch.tensor):
+        """ Get subpart of an image to denoise delimited by x and y as an imaginary and a real part, 
+        symetrise it so that the noises are independant in each part,
+        normalize it and return it as torch tensors
+
+        Args:
+            x (int): x image
+            y (int): y image
+            i_real_part (numpy array): real part of the image
+            i_imag_part (numpy array): imaginary part of the image
+            patch_size (int): patch size of the convolution
+
+        Returns:
+            images to denoise (torch tensor): real and imaginary parts symetrised as torch tensors
+        """
+        real_to_denoise, imag_to_denoise = symetrise_real_and_imaginary_parts(
+            i_real_part[:, x:x + patch_size, y:y + patch_size, :], i_imag_part[:, x:x + patch_size, y:y + patch_size, :])
+
+        real_to_denoise = torch.tensor(
+            real_to_denoise, device=self.device, dtype=torch.float32)
+        imag_to_denoise = torch.tensor(
+            imag_to_denoise, device=self.device, dtype=torch.float32)
+
+        real_to_denoise = (torch.log(torch.square(
+            real_to_denoise)+1e-3)-2*m)/(2*(M-m))
+        imag_to_denoise = (torch.log(torch.square(
+            imag_to_denoise)+1e-3)-2*m)/(2*(M-m))
+
+        return real_to_denoise, imag_to_denoise
+
+    def denoise_image_kernel(self, symetrised_noisy_image: torch.tensor, symetrised_denoised_image: np.array, x: int, y: int, patch_size: int, model: Model, normalisation_kernel: bool = False) -> np.array:
+        """Denoise a subpart of a given symetrised noisy image delimited by x, y and patch_size using a given model
+
+        Args:
+            symetrised_noisy_image (torch tensor): symetrised noisy image to denoise
+            symetrised_denoised_image (numpy array): symetrised partially denoised image
+            x (int): x coordinate of current kernel to denoise
+            y (int): y coordinate of current kernel to denoise
+            patch_size (int): patch size
+            model (Model): trained model with loaded weights 
+            normalisation_kernel (bool, optional): Determine if. Defaults to False.
+
+        Returns:
+            symetrised_denoised_image (numpy array): image denoised in the given coordinates and the ones already iterated
+        """
+        if not normalisation_kernel:
+
+            if self.device != 'cpu':
+                tmp_clean_image = model.forward(
+                    symetrised_noisy_image).cpu().detach().numpy()
+            else:
+                tmp_clean_image = model.forward(
+                    symetrised_noisy_image).detach().numpy()
+
+            tmp_clean_image = np.moveaxis(tmp_clean_image, 1, -1)
+            symetrised_denoised_image[:, x:x + patch_size, y:y + patch_size, :] = symetrised_denoised_image[:, x:x + patch_size,
+                                                                                                            y:y + patch_size,
+                                                                                                            :] + tmp_clean_image
+        else:
+            symetrised_denoised_image[:, x:x + patch_size, y:y + patch_size, :] = symetrised_denoised_image[:, x:x + patch_size,
+                                                                                                            y:y + patch_size,
+                                                                                                            :] + np.ones((1, patch_size, patch_size, 1))
+        return symetrised_denoised_image
+
+    def preprocess_noisy_image(self, noisy_image: np.array) -> (np.array, np.array, np.array):
+        """preprocess a given noisy image and generates its real and imaginary parts
+
+        Args:
+            noisy_image (numpy array): noisy image
+
+        Returns:
+            noisy_image, noisy_image_real_part, noisy_image_imaginary_part (numpy array, numpy array, numpy array): 
+            preprocessed noisy image, real part of noisy image, imaginary part of noisy image
+        """
+        noisy_image_real_part = (noisy_image[:, :, :, 0]).reshape(noisy_image.shape[0], noisy_image.shape[1],
+                                                                  noisy_image.shape[2], 1)
+        noisy_image_imaginary_part = (noisy_image[:, :, :, 1]).reshape(noisy_image.shape[0], noisy_image.shape[1],
+                                                                       noisy_image.shape[2], 1)
+        noisy_image = np.squeeze(
+            np.sqrt(noisy_image_real_part ** 2 + noisy_image_imaginary_part ** 2))
+
+        return noisy_image, noisy_image_real_part, noisy_image_imaginary_part
+
+    def preprocess_denoised_image(self, denoised_image_real_part: np.array, denoised_image_imaginary_part: np.array, count_image: np.array) -> (np.array, np.array, np.array):
+        """Preprocess given denoised real and imaginary parts of an image, and build the full denoised image
+
+        Args:
+            denoised_image_real_part (numpy array): real part of a denoised image
+            denoised_image_imaginary_part (numpy array): imaginary part of a denoised image
+            count_image (numpy array): normalisation image used for denormalisation
+
+        Returns:
+            denoised_image, denoised_image_real_part, denoised_image_imaginary_part (numpy array, numpy array, numpy array): 
+            processed denoised full image, processed denoised image real part, processed denoised image imaginary part
+        """
+        denoised_image_real_part = denormalize_sar_image(
+            denoised_image_real_part / count_image)
+        denoised_image_imaginary_part = denormalize_sar_image(
+            denoised_image_imaginary_part / count_image)
+
+        # combine the two estimation
+        output_clean_image = 0.5 * (np.square(
+            denoised_image_real_part) + np.square(denoised_image_imaginary_part))
+
+        denoised_image = np.sqrt(np.squeeze(output_clean_image))
+
+        return denoised_image, denoised_image_real_part, denoised_image_imaginary_part
+
+    def denoise_image(self, noisy_image: np.array, weights_path: str, patch_size: int, stride_size: int) -> dict:
+        """Preprocess and denoise a coSAR image using given model weights
+
+        Args:
+            noisy_image (numpy array): numpy array containing the noisy image to despeckle 
+            weights_path (str): path to the pth model file
+            patch_size (int): size of the patch of the convolution
+            stride_size (int): number of pixels between one convolution to the next
+
+        Returns:
+            despeckled_image (dict): noisy and denoised images
+        """
+        noisy_image = np.array(noisy_image).reshape(
+            1, np.size(noisy_image, 0), np.size(noisy_image, 1), 2)
+
+        # Pad the image
+        image_height = np.size(noisy_image, 1)
+        image_width = np.size(noisy_image, 2)
+
+        noisy_image, noisy_image_real_part, noisy_image_imaginary_part = self.preprocess_noisy_image(
+            noisy_image)
+
+        model = self.load_model(
+            weights_path=weights_path, patch_size=patch_size)
+
+        count_image = np.zeros(noisy_image_real_part.shape)
+        denoised_image_real_part = np.zeros(noisy_image_real_part.shape)
+        denoised_image_imaginary_part = np.zeros(noisy_image_real_part.shape)
+
+        x_range = self.initialize_axis_range(
+            image_height, patch_size, stride_size)
+        y_range = self.initialize_axis_range(
+            image_width, patch_size, stride_size)
+
+        for x in tqdm(x_range):
+            for y in y_range:
+                real_to_denoise, imag_to_denoise = self.symetrise_real_and_imaginary_kernel(
+                    x, y, noisy_image_real_part, noisy_image_imaginary_part, patch_size)
+
+                denoised_image_real_part = self.denoise_image_kernel(
+                    real_to_denoise, denoised_image_real_part, x, y, patch_size, model)
+                denoised_image_imaginary_part = self.denoise_image_kernel(
+                    imag_to_denoise, denoised_image_imaginary_part, x, y, patch_size, model)
+                count_image = self.denoise_image_kernel(
+                    imag_to_denoise, count_image, x, y, patch_size, model, normalisation_kernel=True)
+
+        denoised_image, denoised_image_real_part, denoised_image_imaginary_part = self.preprocess_denoised_image(
+            denoised_image_real_part, denoised_image_imaginary_part, count_image)
+
+        despeckled_image = {"noisy": {"full": noisy_image,
+                                      "real": np.squeeze(noisy_image_real_part),
+                                      "imaginary": np.squeeze(noisy_image_imaginary_part)
+                                      },
+                            "denoised": {"full": denoised_image,
+                                         "real": denoised_image_real_part,
+                                         "imaginary": denoised_image_imaginary_part
+                                         }
+                            }
+
+        return despeckled_image
diff --git a/deepdespeckling/merlin/test/saved_model/spotlight.pth b/deepdespeckling/merlin/saved_model/spotlight.pth
similarity index 100%
rename from deepdespeckling/merlin/test/saved_model/spotlight.pth
rename to deepdespeckling/merlin/saved_model/spotlight.pth
diff --git a/deepdespeckling/merlin/test/saved_model/stripmap.pth b/deepdespeckling/merlin/saved_model/stripmap.pth
similarity index 100%
rename from deepdespeckling/merlin/test/saved_model/stripmap.pth
rename to deepdespeckling/merlin/saved_model/stripmap.pth
diff --git a/deepdespeckling/merlin/train/Dataset.py b/deepdespeckling/merlin/train/Dataset.py
deleted file mode 100644
index dbb87bc..0000000
--- a/deepdespeckling/merlin/train/Dataset.py
+++ /dev/null
@@ -1,53 +0,0 @@
-import numpy as np
-import torch
-from deepdespeckling.merlin.train.utils import *
-
-
-
-class Dataset(torch.utils.data.Dataset):
-    'characterizes a dataset for pytorch'
-    def __init__(self, patche):
-        self.patches = patche
-
-    def __len__(self):
-        'denotes the total number of samples'
-        return len(self.patches)
-
-    def __getitem__(self, index):
-        'Generates one sample of data'
-        #select sample
-        batch_real = (self.patches[index,:, :, 0])
-        batch_imag = (self.patches[index,:, :, 1])
-        
-        x = torch.tensor(batch_real)
-        y = torch.tensor(batch_imag)
-
-        x = x.unsqueeze(0)
-        y = y.unsqueeze(0)
-
-        return x, y
-
-
-class ValDataset(torch.utils.data.Dataset):
-    'characterizes a dataset for pytorch'
-    def __init__(self, test_set):
-        self.files = glob(test_set+'/*.npy')
-
-    def __len__(self):
-        'denotes the total number of samples'
-        return len(self.files)
-
-    def __getitem__(self, index):
-        'Generates one sample of data'
-        #select sample
-        eval_data = load_sar_images(self.files)
-
-        current_test=eval_data[index]
-
-        current_test[0,:,:,:] = symetrisation_patch(current_test[0,:,:,:])
-        image_real_part = (current_test[:, :, :, 0]).reshape(current_test.shape[0], current_test.shape[1],
-                                                              current_test.shape[2], 1)
-        image_imag_part = (current_test[:, :, :, 1]).reshape(current_test.shape[0], current_test.shape[1],
-                                                              current_test.shape[2], 1)
-
-        return torch.tensor(image_real_part).type(torch.float) , torch.tensor(image_imag_part).type(torch.float) 
diff --git a/deepdespeckling/merlin/train/__init__.py b/deepdespeckling/merlin/train/__init__.py
deleted file mode 100644
index aed35a5..0000000
--- a/deepdespeckling/merlin/train/__init__.py
+++ /dev/null
@@ -1,12 +0,0 @@
-"""Top-level package for merlin."""
-
-__author__ = """youcef kemiche"""
-__email__ = 'y.kemiche06@hotmail.com'
-__version__ = '0.1.0'
-
-
-from deepdespeckling.merlin.train.utils import *
-from deepdespeckling.merlin.train.Dataset import *
-from deepdespeckling.merlin.train.GenerateDataset import *
-from deepdespeckling.merlin.train.model import *
-from deepdespeckling.merlin.train.train import*
\ No newline at end of file
diff --git a/deepdespeckling/merlin/train/model.py b/deepdespeckling/merlin/train/model.py
deleted file mode 100644
index a799a0d..0000000
--- a/deepdespeckling/merlin/train/model.py
+++ /dev/null
@@ -1,306 +0,0 @@
-import numpy as np
-
-from deepdespeckling.merlin.train.utils import *
-from scipy import special
-
-
-# DEFINE PARAMETERS OF SPECKLE AND NORMALIZATION FACTOR
-M = 10.089038980848645
-m = -1.429329123112601
-L = 1
-c = (1 / 2) * (special.psi(L) - np.log(L))
-cn = c / (M - m)  # normalized (0,1) mean of log speckle
-
-import torch
-import numpy as np
-
-
-# Nearest-neighbor up-scaling layer.
-'''def upscale2d(x, factor=2):
-    """ Description
-            ----------
-            Run a nearest-neighbor up-scaling layer for a given factor on an object (image) x
-
-            This is a common quality-enhancing algorithm  that doubles the dimensions of the input.
-
-            Every pixel in low-res is thereafter transformed into 4 pixels.
-
-            Parameters
-            ----------
-            x : an image object
-
-            factor : figure by how many times ou want to multiply the input's initial dimensions
-
-            Returns
-            ----------
-            x if factor is 1
-            or
-            a reshaped x if factor is different from 1
-        """
-    assert isinstance(factor, int) and factor >= 1
-    if factor == 1:
-        return x
-    # with tf.compat.v1.variable_scope('Upscale2D'):
-    s = x.shape
-    x = torch.reshape(x, [-1, s[1], 1, s[2], 1, s[3]])
-    x = torch.tile(x, [1, 1, factor, 1, factor, 1])
-    x = torch.reshape(x, [-1, s[1] , s[2] * factor, s[3]* factor])
-    
-    
-    s = x.shape
-    x = torch.reshape(x, [-1, s[1], s[2], 1, s[3], 1])
-    x = torch.tile(x, [1, 1, 1, factor, 1, factor])
-    x = torch.reshape(x, [-1, s[1], s[2] * factor, s[3] * factor])
-
-    return x'''
-
-
-
-class Model(torch.nn.Module):
-
-    def __init__(self,batch_size,eval_batch_size,device):
-        super().__init__()
-
-        self.batch_size=batch_size
-        self.eval_batch_size=eval_batch_size
-        self.device=device
-
-        self.height = 256
-        self.width = 256
-
-        self.pool = torch.nn.MaxPool2d(kernel_size=2, stride=2)
-        self.leaky = torch.nn.LeakyReLU(0.1)
-
-        self.enc0 = torch.nn.Conv2d(in_channels=1, out_channels=48, kernel_size=(3, 3), stride=(1, 1),
-                                    padding='same')
-        self.enc1 = torch.nn.Conv2d(in_channels=48, out_channels=48, kernel_size=(3, 3), stride=(1, 1),
-                                    padding='same')
-        self.enc2 = torch.nn.Conv2d(in_channels=48, out_channels=48, kernel_size=(3, 3), stride=(1, 1),
-                                    padding='same')
-        self.enc3 = torch.nn.Conv2d(in_channels=48, out_channels=48, kernel_size=(3, 3), stride=(1, 1),
-                                    padding='same')
-        self.enc4 = torch.nn.Conv2d(in_channels=48, out_channels=48, kernel_size=(3, 3), stride=(1, 1),
-                                    padding='same')
-        self.enc5 = torch.nn.Conv2d(in_channels=48, out_channels=48, kernel_size=(3, 3), stride=(1, 1),
-                                    padding='same')
-        self.enc6 = torch.nn.Conv2d(in_channels=48, out_channels=48, kernel_size=(3, 3), stride=(1, 1),
-                                    padding='same')
-
-        self.dec5 = torch.nn.Conv2d(in_channels=96, out_channels=96, kernel_size=(3, 3), stride=(1, 1),
-                                    padding='same')
-        self.dec5b = torch.nn.Conv2d(in_channels=96, out_channels=96, kernel_size=(3, 3), stride=(1, 1),
-                                     padding='same')
-        self.dec4 = torch.nn.Conv2d(in_channels=144, out_channels=96, kernel_size=(3, 3), stride=(1, 1),
-                                    padding='same')
-        self.dec4b = torch.nn.Conv2d(in_channels=96, out_channels=96, kernel_size=(3, 3), stride=(1, 1),
-                                     padding='same')
-        self.dec3 = torch.nn.Conv2d(in_channels=144, out_channels=96, kernel_size=(3, 3), stride=(1, 1),
-                                    padding='same')
-        self.dec3b = torch.nn.Conv2d(in_channels=96, out_channels=96, kernel_size=(3, 3), stride=(1, 1),
-                                     padding='same')
-        self.dec2 = torch.nn.Conv2d(in_channels=144, out_channels=96, kernel_size=(3, 3), stride=(1, 1),
-                                    padding='same')
-        self.dec2b = torch.nn.Conv2d(in_channels=96, out_channels=96, kernel_size=(3, 3), stride=(1, 1),
-                                     padding='same')
-        self.dec1a = torch.nn.Conv2d(in_channels=97, out_channels=64, kernel_size=(3, 3), stride=(1, 1),
-                                     padding='same')
-        self.dec1b = torch.nn.Conv2d(in_channels=64, out_channels=32, kernel_size=(3, 3), stride=(1, 1),
-                                     padding='same')
-        self.dec1 = torch.nn.Conv2d(in_channels=32, out_channels=1, kernel_size=(3, 3), stride=(1, 1),
-                                    padding='same')
-
-        self.upscale2d = torch.nn.UpsamplingNearest2d(scale_factor=2)
-
-
-    def forward(self,x ,batch_size):
-        """  Defines a class for an autoencoder algorithm for an object (image) x
-
-        An autoencoder is a specific type of feedforward neural networks where the
-        input is the same as the
-        output. It compresses the input into a lower-dimensional code and then 
-        reconstruct the output from this representattion. It is a dimensionality 
-        reduction algorithm
-
-        Parameters
-        ----------
-        x : np.array
-        a numpy array containing image 
-
-        Returns
-        ----------
-        x-n : np.array
-        a numpy array containing the denoised image i.e the image itself minus the noise
-
-        """
-      
-        x=torch.reshape(x, [batch_size, 1, self.height, self.width])
-        skips = [x]
-
-        n = x
-
-        # ENCODER
-        n = self.leaky(self.enc0(n))
-        n = self.leaky(self.enc1(n))
-        n = self.pool(n)
-        skips.append(n)
-
-        n = self.leaky(self.enc2(n))
-        n = self.pool(n)
-        skips.append(n)
-
-        n = self.leaky(self.enc3(n))
-        n = self.pool(n)
-        skips.append(n)
-
-        n = self.leaky(self.enc4(n))
-        n = self.pool(n)
-        skips.append(n)
-
-        n = self.leaky(self.enc5(n))
-        n = self.pool(n)
-        n = self.leaky(self.enc6(n))
-
-
-        # DECODER
-        n = self.upscale2d(n)
-        n = torch.cat((n, skips.pop()), dim=1)
-        n = self.leaky(self.dec5(n))
-        n = self.leaky(self.dec5b(n))
-
-        n = self.upscale2d(n)
-        n = torch.cat((n, skips.pop()), dim=1)
-        n = self.leaky(self.dec4(n))
-        n = self.leaky(self.dec4b(n))
-
-        n = self.upscale2d(n)
-        n = torch.cat((n, skips.pop()), dim=1)
-        n = self.leaky(self.dec3(n))
-        n = self.leaky(self.dec3b(n))
-
-        n = self.upscale2d(n)
-        n = torch.cat((n, skips.pop()), dim=1)
-        n = self.leaky(self.dec2(n))
-        n = self.leaky(self.dec2b(n))
-
-        n = self.upscale2d(n)
-        n = torch.cat((n, skips.pop()), dim=1)
-        n = self.leaky(self.dec1a(n))
-        n = self.leaky(self.dec1b(n))
-
-        n = self.dec1(n)
-
-        return x - n
-
-    def loss_function(self,output,target,batch_size):
-      """ Defines and runs the loss function
-        
-      Parameters
-      ----------
-      output : 
-      target :
-      batch_size :
-
-      Returns
-      ----------
-      loss: float
-          The value of loss given your output, target and batch_size
-
-      """
-
-      M = 10.089038980848645
-      m = -1.429329123112601
- 
-      # ----- loss -----
-      log_hat_R = 2*(output*(M-m)+m)
-      hat_R = torch.exp(log_hat_R)+1e-6 # must be nonzero
-      b_square = torch.square(target)
-      loss = (1/batch_size)*torch.mean( 0.5*log_hat_R+b_square/hat_R  ) #+ tf.losses.get_regularization_loss()
-      return loss
- 
-    def training_step(self, batch,batch_number):
-      
-      """ Train the model with the training set
-
-      Parameters
-      ----------
-      batch : a subset of the training date
-      batch_number : ID identifying the batch
-
-      Returns
-      -------
-      loss : float
-        The value of loss given the batch
-        
-      """
-      M = 10.089038980848645
-      m = -1.429329123112601
-
-      x, y = batch
-      x=x.to(self.device)
-      y=y.to(self.device)
-
-
-        
-
-      if (batch_number%2==0):
-        x=(torch.log(torch.square(x)+1e-3)-2*m)/(2*(M-m))
-        out = self.forward(x,self.batch_size)
-        loss = self.loss_function(out, y,self.batch_size)
-
-      else:
-        y=(torch.log(torch.square(y)+1e-3)-2*m)/(2*(M-m))
-        out = self.forward(y,self.batch_size)
-        loss = self.loss_function(out,x,self.batch_size)
-          
-      return loss
-
-    def validation_step(self, batch,image_num,epoch_num,eval_files,eval_set,sample_dir):
-      """ Test the model with the validation set
-
-      Parameters
-      ----------
-      batch : a subset of data
-      image_num : an ID identifying the feeded image
-      epoch_num : an ID identifying the epoch
-      eval_files : .npy files used for evaluation in training
-      eval_set : directory of dataset used for evaluation in training
-
-      Returns
-      ----------
-      output_clean_image : a np.array
-            
-      """
-
-      image_real_part,image_imaginary_part = batch
-
-      image_real_part=image_real_part.to(self.device) 
-      image_imaginary_part=image_imaginary_part.to(self.device)
-
-      # Normalization
-      image_real_part_normalized=(torch.log(torch.square(image_real_part)+1e-3)-2*m)/(2*(M-m))
-      image_imaginary_part_normalized=(torch.log(torch.square(image_imaginary_part)+1e-3)-2*m)/(2*(M-m))
-            
-      out_real = self.forward(image_real_part_normalized,self.eval_batch_size)
-      out_imaginary = self.forward(image_imaginary_part_normalized,self.eval_batch_size)
-
-      output_clean_image = 0.5*(np.square(denormalize_sar(out_real.cpu().numpy()))+np.square(denormalize_sar(out_imaginary.cpu().numpy())))
-      # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # 
-
-      noisyimage = np.squeeze(np.sqrt(np.square(image_real_part.cpu().numpy())+np.square(image_imaginary_part.cpu().numpy())))
-      outputimage = np.sqrt(np.squeeze(output_clean_image))
-
-      # calculate PSNR
-      psnr = cal_psnr(outputimage, noisyimage)
-      print("img%d PSNR: %.2f" % (image_num , psnr))
-
-      # rename and save
-      imagename = eval_files[image_num].replace(eval_set, "")
-      imagename = imagename.replace('.npy', '_epoch_' + str(epoch_num) + '.npy')
-
-      save_sar_images(outputimage, noisyimage, imagename,sample_dir)
-
-      return output_clean_image
-
-
-    # def optimizer(self):
-    #   return torch.optim.Adam
\ No newline at end of file
diff --git a/deepdespeckling/merlin/train/train.py b/deepdespeckling/merlin/train/train.py
deleted file mode 100644
index 75bf228..0000000
--- a/deepdespeckling/merlin/train/train.py
+++ /dev/null
@@ -1,150 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-import argparse
-from glob import glob
-import os
-
-import numpy as np
-import torch
-
-from deepdespeckling.merlin.train.Dataset import *
-from deepdespeckling.merlin.train.model import *
-from deepdespeckling.merlin.train.utils import *
-
-
-def evaluate(model, loader):
-    outputs = [model.validation_step(batch) for batch in loader]
-    outputs = torch.tensor(outputs).T
-    loss, accuracy = torch.mean(outputs, dim=1)
-    return {"loss": loss.item(), "accuracy": accuracy.item()}
-
-
-def save_model(model,destination_folder):
-    """
-      save the ".pth" model in destination_folder
-    """
-    # Check whether the specified path exists or not
-    isExist = os.path.exists(destination_folder)
-
-    if not isExist:
-
-      # Create a new directory because it does not exist
-      os.makedirs(destination_folder)
-      print("The new directory is created!")
-
-      torch.save(model.state_dict(),destination_folder+"/model.pth")
-
-    else:
-      torch.save(model.state_dict(),destination_folder+"/model.pth")
-
-
-def fit(model,train_loader,val_loader,epochs,lr_list,eval_files,eval_set,clip_by_norm,sample_dir):
-  """ Fit the model according to the given evaluation data and parameters.
-
-  Parameters
-  ----------
-  model : model as defined in main
-  train_loader : Pytorch's DataLoader of training data
-  val_loader : Pytorch's DataLoader of validation data
-  lr_list : list of learning rates
-  eval_files : .npy files used for evaluation in training
-  eval_set : directory of dataset used for evaluation in training
-
-  Returns
-  ----------
-  self : object
-    Fitted estimator.
-
-  """
-
-  train_losses = []
-  history={}
-  epoch_num=0
-  for epoch in range(epochs):
-      epoch_num=epoch_num+1
-      print("\nEpoch", epoch+1)
-      print("***************** \n")
-      optimizer=torch.optim.Adam(model.parameters(), lr=lr_list[epoch])
-
-      #Train
-      for i, batch in enumerate(train_loader, 0):
-
-            optimizer.zero_grad()
-            loss = model.training_step(batch,i)
-            train_losses.append(loss)
-
-            loss.backward()
-
-            if(clip_by_norm==True):
-                  
-              torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=5.0)
-
-            optimizer.step()
-
-            #running_loss += loss.item()     # extract the loss value
-            print('[%d, %5d] loss: %.3f' % (epoch + 1, i + 1, loss))
-            # zero the loss
-
-          # #Validate
-      with torch.no_grad():
-        image_num=0
-        for batch in val_loader:
-            val_loss=model.validation_step(batch,image_num,epoch_num,eval_files,eval_set,sample_dir)
-            image_num=image_num+1
-
-  history["train_loss"]=train_losses
-
-  return history
-
-
-def create_model(batch_size=12,val_batch_size=1,device=torch.device("cuda:0" if torch.cuda.is_available() else "cpu"),from_pretrained=False):
-  """ Runs the denoiser algorithm for the training and evaluation dataset
-
-
-  """
-  this_dir, this_filename = os.path.split(__file__)
-  weights_path = os.path.join(this_dir, "saved_model", "model.pth")
-
-  model = Model(batch_size,val_batch_size,device)
-  model.to(device)
-  
-  if from_pretrained == True:
-        
-    model.load_state_dict(torch.load(weights_path, map_location=torch.device('cpu')))
-
-  return model
-
-def fit_model(model,lr_list,nb_epoch,training_set_directory,validation_set_directory,sample_directory,save_directory,patch_size=256,batch_size=12,val_batch_size=1,stride_size=128,n_data_augmentation=1,seed=2,clip_by_norm=True):
-  """ Runs the denoiser algorithm for the training and evaluation dataset
-
-  Parameters
-  ----------
-  model : model as defined in main
-  lr_list : list of learning rates
-
-  Returns
-  ----------
-  history : list of both training and validation loss
-
-  """
-  torch.manual_seed(seed)
-
-
-  # Prepare train DataLoader
-  train_data = load_train_data(training_set_directory,patch_size,batch_size,stride_size,n_data_augmentation) # range [0; 1]
-  train_dataset = Dataset(train_data)
-  train_loader = torch.utils.data.DataLoader(train_dataset,batch_size=batch_size,shuffle=True,drop_last=True)
-
-  # Prepare Validation DataLoader
-  eval_dataset = ValDataset(validation_set_directory)
-  eval_loader = torch.utils.data.DataLoader(eval_dataset,batch_size=val_batch_size,shuffle=False,drop_last=True)
-
-  eval_files = glob(validation_set_directory+'/*.npy')
-  # Train the model
-  history =fit(model,train_loader,eval_loader,nb_epoch,lr_list,eval_files,validation_set_directory,clip_by_norm,sample_directory)
-
-  # Save the model
-  save_model(model,save_directory)
-  print("\n model saved at :",save_directory)
-   
-  return history
diff --git a/deepdespeckling/merlin/train/utils.py b/deepdespeckling/merlin/train/utils.py
deleted file mode 100644
index 469c5ee..0000000
--- a/deepdespeckling/merlin/train/utils.py
+++ /dev/null
@@ -1,271 +0,0 @@
-import gc
-import os
-import sys
-
-import numpy as np
-from PIL import Image
-from scipy import special
-from scipy import signal
-from glob import glob
-from deepdespeckling.merlin.train.GenerateDataset import GenerateDataset
-
-basedir = '.'
-
-# DEFINE PARAMETERS OF SPECKLE AND NORMALIZATION FACTOR
-M = 10.089038980848645
-m = -1.429329123112601
-L = 1
-c = (1 / 2) * (special.psi(L) - np.log(L))
-cn = c / (M - m)  # normalized (0,1) mean of log speckle
-
-
-def symetrisation_patch(ima):
-    S = np.fft.fftshift(np.fft.fft2(ima[:,:,0]+1j*ima[:,:,1]))
-    p = np.zeros((S.shape[0])) # azimut (ncol)
-    for i in range(S.shape[0]):
-        p[i] = np.mean(np.abs(S[i,:]))
-    sp = p[::-1]
-    c = np.real(np.fft.ifft(np.fft.fft(p)*np.conjugate(np.fft.fft(sp))))
-    d1 = np.unravel_index(c.argmax(),p.shape[0])
-    d1 = d1[0]
-    shift_az_1 = int(round(-(d1-1)/2))%p.shape[0]+int(p.shape[0]/2)
-    p2_1 = np.roll(p,shift_az_1)
-    shift_az_2 = int(round(-(d1-1-p.shape[0])/2))%p.shape[0]+int(p.shape[0]/2)
-    p2_2 = np.roll(p,shift_az_2)
-    window = signal.gaussian(p.shape[0], std=0.2*p.shape[0])
-    test_1 = np.sum(window*p2_1)
-    test_2 = np.sum(window*p2_2)
-    # make sure the spectrum is symetrized and zeo-Doppler centered
-    if test_1>=test_2:
-        p2 = p2_1
-        shift_az = shift_az_1/p.shape[0]
-    else:
-        p2 = p2_2
-        shift_az = shift_az_2/p.shape[0]
-    S2 = np.roll(S,int(shift_az*p.shape[0]),axis=0)
-
-    q = np.zeros((S.shape[1])) # range (nlin)
-    for j in range(S.shape[1]):
-        q[j] = np.mean(np.abs(S[:,j]))
-    sq = q[::-1]
-    #correlation
-    cq = np.real(np.fft.ifft(np.fft.fft(q)*np.conjugate(np.fft.fft(sq))))
-    d2 = np.unravel_index(cq.argmax(),q.shape[0])
-    d2=d2[0]
-    shift_range_1 = int(round(-(d2-1)/2))%q.shape[0]+int(q.shape[0]/2)
-    q2_1 = np.roll(q,shift_range_1)
-    shift_range_2 = int(round(-(d2-1-q.shape[0])/2))%q.shape[0]+int(q.shape[0]/2)
-    q2_2 = np.roll(q,shift_range_2)
-    window_r = signal.gaussian(q.shape[0], std=0.2*q.shape[0])
-    test_1 = np.sum(window_r*q2_1)
-    test_2 = np.sum(window_r*q2_2)
-    if test_1>=test_2:
-        q2 = q2_1
-        shift_range = shift_range_1/q.shape[0]
-    else:
-        q2 = q2_2
-        shift_range = shift_range_2/q.shape[0]
-
-
-    Sf = np.roll(S2,int(shift_range*q.shape[0]),axis=1)
-    ima2 = np.fft.ifft2(np.fft.ifftshift(Sf))
-    return np.stack((np.real(ima2),np.imag(ima2)),axis=2)
-
-
-def symetrisation_patch_test(real_part,imag_part):
-    S = np.fft.fftshift(np.fft.fft2(real_part[0,:,:,0]+1j*imag_part[0,:,:,0]))
-    p = np.zeros((S.shape[0])) # azimut (ncol)
-    for i in range(S.shape[0]):
-        p[i] = np.mean(np.abs(S[i,:]))
-    sp = p[::-1]
-    c = np.real(np.fft.ifft(np.fft.fft(p)*np.conjugate(np.fft.fft(sp))))
-    d1 = np.unravel_index(c.argmax(),p.shape[0])
-    d1 = d1[0]
-    shift_az_1 = int(round(-(d1-1)/2))%p.shape[0]+int(p.shape[0]/2)
-    p2_1 = np.roll(p,shift_az_1)
-    shift_az_2 = int(round(-(d1-1-p.shape[0])/2))%p.shape[0]+int(p.shape[0]/2)
-    p2_2 = np.roll(p,shift_az_2)
-    window = signal.gaussian(p.shape[0], std=0.2*p.shape[0])
-    test_1 = np.sum(window*p2_1)
-    test_2 = np.sum(window*p2_2)
-    # make sure the spectrum is symetrized and zeo-Doppler centered
-    if test_1>=test_2:
-        p2 = p2_1
-        shift_az = shift_az_1/p.shape[0]
-    else:
-        p2 = p2_2
-        shift_az = shift_az_2/p.shape[0]
-    S2 = np.roll(S,int(shift_az*p.shape[0]),axis=0)
-
-    q = np.zeros((S.shape[1])) # range (nlin)
-    for j in range(S.shape[1]):
-        q[j] = np.mean(np.abs(S[:,j]))
-    sq = q[::-1]
-    #correlation
-    cq = np.real(np.fft.ifft(np.fft.fft(q)*np.conjugate(np.fft.fft(sq))))
-    d2 = np.unravel_index(cq.argmax(),q.shape[0])
-    d2=d2[0]
-    shift_range_1 = int(round(-(d2-1)/2))%q.shape[0]+int(q.shape[0]/2)
-    q2_1 = np.roll(q,shift_range_1)
-    shift_range_2 = int(round(-(d2-1-q.shape[0])/2))%q.shape[0]+int(q.shape[0]/2)
-    q2_2 = np.roll(q,shift_range_2)
-    window_r = signal.gaussian(q.shape[0], std=0.2*q.shape[0])
-    test_1 = np.sum(window_r*q2_1)
-    test_2 = np.sum(window_r*q2_2)
-    if test_1>=test_2:
-        q2 = q2_1
-        shift_range = shift_range_1/q.shape[0]
-    else:
-        q2 = q2_2
-        shift_range = shift_range_2/q.shape[0]
-
-
-    Sf = np.roll(S2,int(shift_range*q.shape[0]),axis=1)
-    ima2 = np.fft.ifft2(np.fft.ifftshift(Sf))
-    ima2 = ima2.reshape(1, np.size(ima2, 0), np.size(ima2, 1), 1)
-    return np.real(ima2), np.imag(ima2)
-
-
-class train_data():
-    def __init__(self, filepath="%s/data/image_clean_pat.npy" % basedir):
-        self.filepath = filepath
-        assert '.npy' in filepath
-        if not os.path.exists(filepath):
-            print("[!] Data file not exists")
-            sys.exit(1)
-
-    def __enter__(self):
-        print("[*] Loading data...")
-        self.data = np.load(self.filepath)
-        np.random.shuffle(self.data)
-        print("[*] Load successfully...")
-        return self.data
-
-    def __exit__(self, type, value, trace):
-        del self.data
-        gc.collect()
-        print("In __exit__()")
-
-
-def load_data(filepath="%s/data/image_clean_pat.npy" % basedir):
-    return train_data(filepath=filepath)
-
-
-def normalize_sar(im):
-    return ((np.log(im+1e-6)-m)/(M-m)).astype(np.float32)
-
-def denormalize_sar(im):
-    return np.exp((np.clip(np.squeeze(im),0,1))*(M-m)+m)
-
-def load_train_data(filepath, patch_size, batch_size, stride_size, n_data_augmentation): #TODO: add control on training data: exit if does not exists
-    datagen = GenerateDataset()
-    imgs = datagen.generate_patches(src_dir=filepath, pat_size=patch_size, step=0,
-                             stride=stride_size, bat_size=batch_size, data_aug_times=n_data_augmentation)
-    return imgs
-
-def load_sar_images(filelist):
-    if not isinstance(filelist, list):
-        im = np.load(filelist)
-        return np.array(im).reshape(1, np.size(im, 0), np.size(im, 1), 2)
-    data = []
-    for file in filelist:
-        im = np.load(file)
-        data.append(np.array(im).reshape(1, np.size(im, 0), np.size(im, 1), 2))
-    return data
-
-
-
-def store_data_and_plot(im, threshold, filename):
-    im = np.clip(im, 0, threshold)
-    im = im / threshold * 255
-    im = Image.fromarray(im.astype('float64')).convert('L')
-    im.save(filename.replace('npy','png'))
-
-
-def save_sar_images(denoised, noisy, imagename, save_dir, groundtruth=None):
-    choices = {'marais1':190.92, 'marais2': 168.49, 'saclay':470.92, 'lely':235.90, 'ramb':167.22,
-           'risoul':306.94, 'limagne':178.43, 'saintgervais':560, 'Serreponcon': 450.0,
-          'Sendai':600.0, 'Paris': 1291.0, 'Berlin': 1036.0, 'Bergen': 553.71,
-          'SDP_Lambesc': 349.53, 'Grand_Canyon': 287.0, 'domancy': 560, 'Brazil': 103.0}
-    threshold = None
-    for x in choices:
-        if x in imagename:
-            threshold = choices.get(x)
-    if threshold is None: threshold = np.mean(noisy) + 3 * np.std(noisy)
-
-    if groundtruth:
-        groundtruthfilename = save_dir+"/groundtruth_"+imagename
-        groundtruthfilename=groundtruthfilename.replace("\\","")
-
-        np.save(groundtruthfilename,groundtruth)
-        store_data_and_plot(groundtruth, threshold, groundtruthfilename)
-
-    denoisedfilename = save_dir + "/denoised_" + imagename
-    denoisedfilename=denoisedfilename.replace("\\","")
-
-    np.save(denoisedfilename, denoised)
-    store_data_and_plot(denoised, threshold, denoisedfilename)
-
-    noisyfilename = save_dir + "/noisy_" + imagename
-    noisyfilename=noisyfilename.replace("\\","")
-
-    np.save(noisyfilename, noisy)
-    store_data_and_plot(noisy, threshold, noisyfilename)
-
-
-def save_real_imag_images(real_part, imag_part, imagename, save_dir):
-    choices = {'marais1': 190.92, 'marais2': 168.49, 'saclay': 470.92, 'lely': 235.90, 'ramb': 167.22,
-               'risoul': 306.94, 'limagne': 178.43, 'saintgervais': 560, 'Serreponcon': 450.0,
-               'Sendai': 600.0, 'Paris': 1291.0, 'Berlin': 1036.0, 'Bergen': 553.71,
-               'SDP_Lambesc': 349.53, 'Grand_Canyon': 287.0, 'Brazil': 103.0}
-    threshold = None
-    for x in choices:
-        if x in imagename:
-            threshold = choices.get(x)
-    if threshold is None: threshold = np.mean(imag_part) + 3 * np.std(imag_part)
-
-    realfilename = save_dir + "/denoised_real_" + imagename
-    realfilename=realfilename.replace("\\","")
-
-    np.save(realfilename, real_part)
-    store_data_and_plot(real_part, threshold, realfilename)
-
-    imagfilename = save_dir + "/denoised_imag_" + imagename
-    imagfilename=imagfilename.replace("\\","")
-
-    np.save(imagfilename, imag_part)
-    store_data_and_plot(imag_part, threshold, imagfilename)
-
-def save_real_imag_images_noisy(real_part, imag_part, imagename, save_dir):
-    choices = {'marais1': 190.92, 'marais2': 168.49, 'saclay': 470.92, 'lely': 235.90, 'ramb': 167.22,
-               'risoul': 306.94, 'limagne': 178.43, 'saintgervais': 560, 'Serreponcon': 450.0,
-               'Sendai': 600.0, 'Paris': 1291.0, 'Berlin': 1036.0, 'Bergen': 553.71,
-               'SDP_Lambesc': 349.53, 'Grand_Canyon': 287.0, 'Brazil': 103.0}
-    threshold = None
-    for x in choices:
-        if x in imagename:
-            threshold = choices.get(x)
-    if threshold is None: threshold = np.mean(np.abs(imag_part)) + 3 * np.std(np.abs(imag_part))
-
-    realfilename = save_dir + "/noisy_real_" + imagename
-    realfilename=realfilename.replace("\\","")
-
-    np.save(realfilename, real_part)
-    store_data_and_plot(np.sqrt(2)*np.abs(real_part), threshold, realfilename)
-
-    imagfilename = save_dir + "/noisy_imag_" + imagename
-    imagfilename=imagfilename.replace("\\","")
-    
-    np.save(imagfilename, imag_part)
-    store_data_and_plot(np.sqrt(2)*np.abs(imag_part), threshold, imagfilename)
-
-
-def cal_psnr(Shat, S):
-    # takes amplitudes in input
-    # Shat: a SAR amplitude image
-    # S:    a reference SAR image
-    P = np.quantile(S, 0.99)
-    res = 10 * np.log10((P ** 2) / np.mean(np.abs(Shat - S) ** 2))
-    return res
-
-
diff --git a/deepdespeckling/merlin/train/.directory b/deepdespeckling/merlin/training/.directory
similarity index 100%
rename from deepdespeckling/merlin/train/.directory
rename to deepdespeckling/merlin/training/.directory
diff --git a/tests/sar2sar/train/Dataset.py b/deepdespeckling/merlin/training/Dataset.py
similarity index 58%
rename from tests/sar2sar/train/Dataset.py
rename to deepdespeckling/merlin/training/Dataset.py
index c7148c5..be7ace1 100644
--- a/tests/sar2sar/train/Dataset.py
+++ b/deepdespeckling/merlin/training/Dataset.py
@@ -1,11 +1,13 @@
+from glob import glob
 import numpy as np
 import torch
-from merlinsar.train.utils import *
 
+from deepdespeckling.utils.utils import load_sar_images, symetrise_real_and_imaginary_parts
 
 
 class Dataset(torch.utils.data.Dataset):
     'characterizes a dataset for pytorch'
+
     def __init__(self, patche):
         self.patches = patche
 
@@ -15,10 +17,10 @@ def __len__(self):
 
     def __getitem__(self, index):
         'Generates one sample of data'
-        #select sample
-        batch_real = (self.patches[index,:, :, 0])
-        batch_imag = (self.patches[index,:, :, 1])
-        
+        # select sample
+        batch_real = (self.patches[index, :, :, 0])
+        batch_imag = (self.patches[index, :, :, 1])
+
         x = torch.tensor(batch_real)
         y = torch.tensor(batch_imag)
 
@@ -30,6 +32,7 @@ def __getitem__(self, index):
 
 class ValDataset(torch.utils.data.Dataset):
     'characterizes a dataset for pytorch'
+
     def __init__(self, test_set):
         self.files = glob(test_set+'/*.npy')
 
@@ -39,15 +42,19 @@ def __len__(self):
 
     def __getitem__(self, index):
         'Generates one sample of data'
-        #select sample
+        # select sample
         eval_data = load_sar_images(self.files)
 
-        current_test=eval_data[index]
+        current_test = eval_data[index]
+
+        real_part, imaginary_part = symetrise_real_and_imaginary_parts(
+            current_test[0, :, :, :])
+        current_test[0, :, :, :] = np.stack(
+            (real_part, imaginary_part), axis=2)
 
-        current_test[0,:,:,:] = symetrisation_patch(current_test[0,:,:,:])
         image_real_part = (current_test[:, :, :, 0]).reshape(current_test.shape[0], current_test.shape[1],
-                                                              current_test.shape[2], 1)
+                                                             current_test.shape[2], 1)
         image_imag_part = (current_test[:, :, :, 1]).reshape(current_test.shape[0], current_test.shape[1],
-                                                              current_test.shape[2], 1)
+                                                             current_test.shape[2], 1)
 
-        return torch.tensor(image_real_part).type(torch.float) , torch.tensor(image_imag_part).type(torch.float) 
+        return torch.tensor(image_real_part).type(torch.float), torch.tensor(image_imag_part).type(torch.float)
diff --git a/deepdespeckling/merlin/train/GenerateDataset.py b/deepdespeckling/merlin/training/GenerateDataset.py
similarity index 63%
rename from deepdespeckling/merlin/train/GenerateDataset.py
rename to deepdespeckling/merlin/training/GenerateDataset.py
index c605613..3428973 100644
--- a/deepdespeckling/merlin/train/GenerateDataset.py
+++ b/deepdespeckling/merlin/training/GenerateDataset.py
@@ -1,9 +1,8 @@
 import glob
-import random
-import os
 import numpy as np
 from scipy import signal
-from scipy import special
+
+from deepdespeckling.utils.constants import PATCH_SIZE
 
 
 '''
@@ -11,6 +10,7 @@
 The code scans among the training images and then for data_aug_times
 '''
 
+
 class GenerateDataset():
     def data_augmentation(self, image, mode):
         if mode == 0:
@@ -42,65 +42,59 @@ def data_augmentation(self, image, mode):
             return np.flipud(image)
 
     def symetrisation_patch_gen(self, ima):
-        S = np.fft.fftshift(np.fft.fft2(ima[:,:,0]+1j*ima[:,:,1]))
-        p = np.zeros((S.shape[0])) # azimut (ncol)
+        S = np.fft.fftshift(np.fft.fft2(ima[:, :, 0]+1j*ima[:, :, 1]))
+        p = np.zeros((S.shape[0]))  # azimut (ncol)
         for i in range(S.shape[0]):
-            p[i] = np.mean(np.abs(S[i,:]))
+            p[i] = np.mean(np.abs(S[i, :]))
         sp = p[::-1]
         c = np.real(np.fft.ifft(np.fft.fft(p)*np.conjugate(np.fft.fft(sp))))
-        d1 = np.unravel_index(c.argmax(),p.shape[0])
+        d1 = np.unravel_index(c.argmax(), p.shape[0])
         d1 = d1[0]
-        shift_az_1 = int(round(-(d1-1)/2))%p.shape[0]+int(p.shape[0]/2)
-        p2_1 = np.roll(p,shift_az_1)
-        shift_az_2 = int(round(-(d1-1-p.shape[0])/2))%p.shape[0]+int(p.shape[0]/2)
-        p2_2 = np.roll(p,shift_az_2)
+        shift_az_1 = int(round(-(d1-1)/2)) % p.shape[0]+int(p.shape[0]/2)
+        p2_1 = np.roll(p, shift_az_1)
+        shift_az_2 = int(
+            round(-(d1-1-p.shape[0])/2)) % p.shape[0]+int(p.shape[0]/2)
+        p2_2 = np.roll(p, shift_az_2)
         window = signal.gaussian(p.shape[0], std=0.2*p.shape[0])
         test_1 = np.sum(window*p2_1)
         test_2 = np.sum(window*p2_2)
         # make sure the spectrum is symetrized and zeo-Doppler centered
-        if test_1>=test_2:
+        if test_1 >= test_2:
             p2 = p2_1
             shift_az = shift_az_1/p.shape[0]
         else:
             p2 = p2_2
             shift_az = shift_az_2/p.shape[0]
-        S2 = np.roll(S,int(shift_az*p.shape[0]),axis=0)
-
-
+        S2 = np.roll(S, int(shift_az*p.shape[0]), axis=0)
 
-
-        q = np.zeros((S.shape[1])) # range (nlin)
+        q = np.zeros((S.shape[1]))  # range (nlin)
         for j in range(S.shape[1]):
-            q[j] = np.mean(np.abs(S[:,j]))
+            q[j] = np.mean(np.abs(S[:, j]))
         sq = q[::-1]
-        #correlation
+        # correlation
         cq = np.real(np.fft.ifft(np.fft.fft(q)*np.conjugate(np.fft.fft(sq))))
-        d2 = np.unravel_index(cq.argmax(),q.shape[0])
-        d2=d2[0]
-        shift_range_1 = int(round(-(d2-1)/2))%q.shape[0]+int(q.shape[0]/2)
-        q2_1 = np.roll(q,shift_range_1)
-        shift_range_2 = int(round(-(d2-1-q.shape[0])/2))%q.shape[0]+int(q.shape[0]/2)
-        q2_2 = np.roll(q,shift_range_2)
+        d2 = np.unravel_index(cq.argmax(), q.shape[0])
+        d2 = d2[0]
+        shift_range_1 = int(round(-(d2-1)/2)) % q.shape[0]+int(q.shape[0]/2)
+        q2_1 = np.roll(q, shift_range_1)
+        shift_range_2 = int(
+            round(-(d2-1-q.shape[0])/2)) % q.shape[0]+int(q.shape[0]/2)
+        q2_2 = np.roll(q, shift_range_2)
         window_r = signal.gaussian(q.shape[0], std=0.2*q.shape[0])
         test_1 = np.sum(window_r*q2_1)
         test_2 = np.sum(window_r*q2_2)
-        if test_1>=test_2:
+        if test_1 >= test_2:
             q2 = q2_1
             shift_range = shift_range_1/q.shape[0]
         else:
             q2 = q2_2
             shift_range = shift_range_2/q.shape[0]
 
-
-        Sf = np.roll(S2,int(shift_range*q.shape[0]),axis=1)
+        Sf = np.roll(S2, int(shift_range*q.shape[0]), axis=1)
         ima2 = np.fft.ifft2(np.fft.ifftshift(Sf))
-        return np.stack((np.real(ima2),np.imag(ima2)),axis=2)
-
+        return np.stack((np.real(ima2), np.imag(ima2)), axis=2)
 
-
-
-
-    def generate_patches(self,src_dir="./dataset/data/Train",pat_size=256,step=0,stride=64,bat_size=4,data_aug_times=1,n_channels=2):
+    def generate_patches(self, src_dir="./dataset/data/Train", patch_size=PATCH_SIZE, step=0, stride=64, bat_size=4, data_aug_times=1, n_channels=2):
         count = 0
         filepaths = glob.glob(src_dir + '/*.npy')
         print("number of training data %d" % len(filepaths))
@@ -111,8 +105,8 @@ def generate_patches(self,src_dir="./dataset/data/Train",pat_size=256,step=0,str
 
             im_h = np.size(img, 0)
             im_w = np.size(img, 1)
-            for x in range(0+step, (im_h - pat_size), stride):
-                for y in range(0+step, (im_w - pat_size), stride):
+            for x in range(0+step, (im_h - patch_size), stride):
+                for y in range(0+step, (im_w - patch_size), stride):
                     count += 1
         origin_patch_num = count * data_aug_times
 
@@ -120,43 +114,42 @@ def generate_patches(self,src_dir="./dataset/data/Train",pat_size=256,step=0,str
             numPatches = (origin_patch_num / bat_size + 1) * bat_size
         else:
             numPatches = origin_patch_num
-        print("total patches = %d , batch size = %d, total batches = %d" % \
+        print("total patches = %d , batch size = %d, total batches = %d" %
               (numPatches, bat_size, numPatches / bat_size))
 
         # data matrix 4-D
-        numPatches=int(numPatches)
-        inputs = np.zeros((numPatches, pat_size, pat_size, n_channels), dtype="float32")
-
+        numPatches = int(numPatches)
+        inputs = np.zeros((numPatches, patch_size, patch_size,
+                          n_channels), dtype="float32")
 
         count = 0
         # generate patches
-        for i in range(len(filepaths)): #scan through images
+        for i in range(len(filepaths)):  # scan through images
             img = np.load(filepaths[i])
             img_s = img
 
-
             # If data_aug_times = 8 then perform them all, otherwise pick one at random or do nothing
             for j in range(data_aug_times):
                 im_h = np.size(img, 0)
                 im_w = np.size(img, 1)
                 if data_aug_times == 8:
-                    for x in range(0 + step, im_h - pat_size, stride):
-                        for y in range(0 + step, im_w - pat_size, stride):
-                            inputs[count, :, :, :] = self.data_augmentation(img_s[x:x + pat_size, y:y + pat_size, :], \
-                                  j)
+                    for x in range(0 + step, im_h - patch_size, stride):
+                        for y in range(0 + step, im_w - patch_size, stride):
+                            inputs[count, :, :, :] = self.data_augmentation(img_s[x:x + patch_size, y:y + patch_size, :],
+                                                                            j)
                             count += 1
                 else:
-                    for x in range(0 + step, im_h - pat_size, stride):
-                        for y in range(0 + step, im_w - pat_size, stride):
+                    for x in range(0 + step, im_h - patch_size, stride):
+                        for y in range(0 + step, im_w - patch_size, stride):
                             # to pick one at random, uncomment this line and comment the one below
-                            """inputs[count, :, :, :] = self.data_augmentation(img_s[x:x + pat_size, y:y + pat_size, :], \
+                            """inputs[count, :, :, :] = self.data_augmentation(img_s[x:x + patch_size, y:y + patch_size, :], \
                                                                           random.randint(0, 7))"""
 
-                            inputs[count, :, :, :] = self.data_augmentation(self.symetrisation_patch_gen(img_s[x:x + pat_size, y:y + pat_size, :]),0)
+                            inputs[count, :, :, :] = self.data_augmentation(
+                                self.symetrisation_patch_gen(img_s[x:x + patch_size, y:y + patch_size, :]), 0)
 
                             count += 1
 
-
         # pad the batch
         if count < numPatches:
             to_pad = numPatches - count
diff --git a/deepdespeckling/merlin/training/__init__.py b/deepdespeckling/merlin/training/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/deepdespeckling/merlin/test/model.py b/deepdespeckling/merlin/training/model.py
similarity index 52%
rename from deepdespeckling/merlin/test/model.py
rename to deepdespeckling/merlin/training/model.py
index e96989f..69a0602 100644
--- a/deepdespeckling/merlin/test/model.py
+++ b/deepdespeckling/merlin/training/model.py
@@ -1,35 +1,21 @@
-import time
 import numpy as np
-import os
-
-from deepdespeckling.merlin.test.utils import *
-from scipy import special
-import argparse
-
-
-# DEFINE PARAMETERS OF SPECKLE AND NORMALIZATION FACTOR
-M = 10.089038980848645
-m = -1.429329123112601
-L = 1
-c = (1 / 2) * (special.psi(L) - np.log(L))
-cn = c / (M - m)  # normalized (0,1) mean of log speckle
-
 import torch
-import numpy as np
-
 
+from deepdespeckling.utils.constants import M, PATCH_SIZE, m
+from deepdespeckling.utils.utils import compute_psnr, save_image_to_png
 
 
 class Model(torch.nn.Module):
 
-    def __init__(self,device):
+    def __init__(self, batch_size, eval_batch_size, device):
         super().__init__()
 
-        self.device=device
-
-        self.height = 256
-        self.width = 256
+        self.batch_size = batch_size
+        self.eval_batch_size = eval_batch_size
+        self.device = device
 
+        self.height = PATCH_SIZE
+        self.width = PATCH_SIZE
 
         self.pool = torch.nn.MaxPool2d(kernel_size=2, stride=2)
         self.leaky = torch.nn.LeakyReLU(0.1)
@@ -74,8 +60,7 @@ def __init__(self,device):
 
         self.upscale2d = torch.nn.UpsamplingNearest2d(scale_factor=2)
 
-
-    def forward(self,x):
+    def forward(self, x, batch_size):
         """  Defines a class for an autoencoder algorithm for an object (image) x
 
         An autoencoder is a specific type of feedforward neural networks where the
@@ -95,7 +80,8 @@ def forward(self,x):
         a numpy array containing the denoised image i.e the image itself minus the noise
 
         """
-        x=torch.reshape(x, [1, 1, self.height, self.width])
+
+        x = torch.reshape(x, [batch_size, 1, self.height, self.width])
         skips = [x]
 
         n = x
@@ -122,7 +108,6 @@ def forward(self,x):
         n = self.pool(n)
         n = self.leaky(self.enc6(n))
 
-
         # DECODER
         n = self.upscale2d(n)
         n = torch.cat((n, skips.pop()), dim=1)
@@ -152,3 +137,128 @@ def forward(self,x):
         n = self.dec1(n)
 
         return x - n
+
+    def loss_function(self, output, target, batch_size):
+        """ Defines and runs the loss function
+
+        Parameters
+        ----------
+        output : 
+        target :
+        batch_size :
+
+        Returns
+        ----------
+        loss: float
+            The value of loss given your output, target and batch_size
+
+        """
+
+        # ----- loss -----
+        log_hat_R = 2*(output*(M-m)+m)
+        hat_R = torch.exp(log_hat_R)+1e-6  # must be nonzero
+        b_square = torch.square(target)
+        # + tf.losses.get_regularization_loss()
+        loss = (1/batch_size)*torch.mean(0.5*log_hat_R+b_square/hat_R)
+        return loss
+
+    def training_step(self, batch, batch_number):
+        """ Train the model with the training set
+
+        Parameters
+        ----------
+        batch : a subset of the training date
+        batch_number : ID identifying the batch
+
+        Returns
+        -------
+        loss : float
+          The value of loss given the batch
+
+        """
+
+        x, y = batch
+        x = x.to(self.device)
+        y = y.to(self.device)
+
+        if (batch_number % 2 == 0):
+            x = (torch.log(torch.square(x)+1e-3)-2*m)/(2*(M-m))
+            out = self.forward(x, self.batch_size)
+            loss = self.loss_function(out, y, self.batch_size)
+
+        else:
+            y = (torch.log(torch.square(y)+1e-3)-2*m)/(2*(M-m))
+            out = self.forward(y, self.batch_size)
+            loss = self.loss_function(out, x, self.batch_size)
+
+        return loss
+
+    def denormalize_sar_for_training(self, image):
+        """Denormalization of a numpy image for merlin training
+
+        Args:
+            image (numpy array): a cosar image
+
+        Returns:
+            numpy array : the image denormalized
+        """
+        return np.exp((np.clip(np.squeeze(image), 0, 1))*(M-m)+m)
+
+    def validation_step(self, batch, image_num, epoch_num, eval_files, eval_set, sample_dir):
+        """ Test the model with the validation set
+
+        Parameters
+        ----------
+        batch : a subset of data
+        image_num : an ID identifying the feeded image
+        epoch_num : an ID identifying the epoch
+        eval_files : .npy files used for evaluation in training
+        eval_set : directory of dataset used for evaluation in training
+
+        Returns
+        ----------
+        output_clean_image : a np.array
+
+        """
+
+        image_real_part, image_imaginary_part = batch
+
+        image_real_part = image_real_part.to(self.device)
+        image_imaginary_part = image_imaginary_part.to(self.device)
+
+        # Normalization
+        image_real_part_normalized = (
+            torch.log(torch.square(image_real_part)+1e-3)-2*m)/(2*(M-m))
+        image_imaginary_part_normalized = (
+            torch.log(torch.square(image_imaginary_part)+1e-3)-2*m)/(2*(M-m))
+
+        out_real = self.forward(
+            image_real_part_normalized, self.eval_batch_size)
+        out_imaginary = self.forward(
+            image_imaginary_part_normalized, self.eval_batch_size)
+
+        output_clean_image = 0.5*(np.square(self.denormalize_sar_for_training(out_real.cpu(
+        ).numpy()))+np.square(self.denormalize_sar_for_training(out_imaginary.cpu().numpy())))
+        # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+
+        noisyimage = np.squeeze(np.sqrt(np.square(
+            image_real_part.cpu().numpy())+np.square(image_imaginary_part.cpu().numpy())))
+        outputimage = np.sqrt(np.squeeze(output_clean_image))
+
+        # calculate PSNR
+        psnr = compute_psnr(outputimage, noisyimage)
+        print("img%d PSNR: %.2f" % (image_num, psnr))
+
+        # rename and save
+        imagename = eval_files[image_num].replace(eval_set, "")
+        imagename = imagename.replace(
+            '.npy', '_epoch_' + str(epoch_num) + '.npy')
+
+        threshold = np.mean(noisyimage) + 3 * np.std(noisyimage)
+        imagename = imagename.split('\\')[-1]
+        save_image_to_png(outputimage, sample_dir,
+                          "/denoised_", imagename, threshold)
+        save_image_to_png(noisyimage, sample_dir,
+                          "/noisy_", imagename, threshold)
+
+        return output_clean_image
diff --git a/deepdespeckling/merlin/train/saved_model/model.pth b/deepdespeckling/merlin/training/saved_model/model.pth
similarity index 100%
rename from deepdespeckling/merlin/train/saved_model/model.pth
rename to deepdespeckling/merlin/training/saved_model/model.pth
diff --git a/deepdespeckling/merlin/training/train.py b/deepdespeckling/merlin/training/train.py
new file mode 100644
index 0000000..29cb3cb
--- /dev/null
+++ b/deepdespeckling/merlin/training/train.py
@@ -0,0 +1,155 @@
+from glob import glob
+import os
+import torch
+from deepdespeckling.merlin.training.GenerateDataset import GenerateDataset
+
+from deepdespeckling.merlin.training.Dataset import Dataset, ValDataset
+from deepdespeckling.merlin.training.model import Model
+from deepdespeckling.utils.constants import PATCH_SIZE
+
+
+def evaluate(model, loader):
+    outputs = [model.validation_step(batch) for batch in loader]
+    outputs = torch.tensor(outputs).T
+    loss, accuracy = torch.mean(outputs, dim=1)
+    return {"loss": loss.item(), "accuracy": accuracy.item()}
+
+
+def save_model(model, destination_folder):
+    """Save the given model to the given destination folder
+
+    Args:
+        model (torch model): a trained torch model
+        destination_folder (str): path of a folder where to store the model
+    """
+    # Check whether the specified path exists or not
+    isExist = os.path.exists(destination_folder)
+
+    if not isExist:
+        # Create a new directory because it does not exist
+        os.makedirs(destination_folder)
+        print("The new directory is created!")
+        torch.save(model.state_dict(), destination_folder+"/model.pth")
+    else:
+        torch.save(model.state_dict(), destination_folder+"/model.pth")
+
+
+def fit(model, train_loader, val_loader, epochs, lr_list, eval_files, eval_set, clip_by_norm, sample_dir):
+    """ Fit the model according to the given evaluation data and parameters.
+
+    Parameters
+    ----------
+      model : model as defined in main
+      train_loader : Pytorch's DataLoader of training data
+      val_loader : Pytorch's DataLoader of validation data
+      lr_list : list of learning rates
+      eval_files : .npy files used for evaluation in training
+      eval_set : directory of dataset used for evaluation in training
+
+    Returns
+    ----------
+      self : object
+        Fitted estimator.
+
+    """
+
+    train_losses = []
+    history = {}
+    epoch_num = 0
+    for epoch in range(epochs):
+        epoch_num = epoch_num + 1
+        print("\nEpoch", epoch + 1)
+        print("***************** \n")
+        optimizer = torch.optim.Adam(model.parameters(), lr=lr_list[epoch])
+
+        # Train
+        for i, batch in enumerate(train_loader, 0):
+            optimizer.zero_grad()
+            loss = model.training_step(batch, i)
+            train_losses.append(loss)
+
+            loss.backward()
+
+            if (clip_by_norm == True):
+
+                torch.nn.utils.clip_grad_norm_(
+                    model.parameters(), max_norm=5.0)
+
+            optimizer.step()
+
+            # running_loss += loss.item()     # extract the loss value
+            print('[%d, %5d] loss: %.3f' % (epoch + 1, i + 1, loss))
+            # zero the loss
+
+        # Validate
+        with torch.no_grad():
+            image_num = 0
+            for batch in val_loader:
+                val_loss = model.validation_step(
+                    batch, image_num, epoch_num, eval_files, eval_set, sample_dir)
+                image_num = image_num+1
+
+    history["train_loss"] = train_losses
+
+    return history
+
+
+def create_model(batch_size=12, val_batch_size=1, device=torch.device("cuda:0" if torch.cuda.is_available() else "cpu"),
+                 from_pretrained=False):
+    """ Runs the denoiser algorithm for the training and evaluation dataset
+
+
+    """
+    this_dir, this_filename = os.path.split(__file__)
+    weights_path = os.path.join(this_dir, "saved_model", "model.pth")
+
+    model = Model(batch_size, val_batch_size, device)
+    model.to(device)
+
+    if from_pretrained == True:
+        model.load_state_dict(torch.load(
+            weights_path, map_location=torch.device('cpu')))
+
+    return model
+
+
+def fit_model(model, lr_list, nb_epoch, training_set_directory, validation_set_directory, sample_directory,
+              save_directory, patch_size=PATCH_SIZE, batch_size=12, val_batch_size=1, stride_size=128,
+              n_data_augmentation=1, seed=2, clip_by_norm=True):
+    """ Runs the denoiser algorithm for the training and evaluation dataset
+
+    Parameters
+    ----------
+    model : model as defined in main
+    lr_list : list of learning rates
+
+    Returns
+    ----------
+    history : list of both training and validation loss
+
+    """
+    torch.manual_seed(seed)
+
+    train_data = GenerateDataset().generate_patches(src_dir=training_set_directory, patch_size=patch_size, step=0,
+                                                    stride=stride_size, bat_size=batch_size, data_aug_times=n_data_augmentation)
+
+    # Prepare train DataLoader
+    train_dataset = Dataset(train_data)
+    train_loader = torch.utils.data.DataLoader(
+        train_dataset, batch_size=batch_size, shuffle=True, drop_last=True)
+
+    # Prepare Validation DataLoader
+    eval_dataset = ValDataset(validation_set_directory)
+    eval_loader = torch.utils.data.DataLoader(
+        eval_dataset, batch_size=val_batch_size, shuffle=False, drop_last=True)
+
+    eval_files = glob(validation_set_directory+'/*.npy')
+    # Train the model
+    history = fit(model, train_loader, eval_loader, nb_epoch, lr_list, eval_files, validation_set_directory,
+                  clip_by_norm, sample_directory)
+
+    # Save the model
+    save_model(model, save_directory)
+    print("\n model saved at :", save_directory)
+
+    return history
diff --git a/deepdespeckling/sar2sar/test/model.py b/deepdespeckling/model.py
similarity index 73%
rename from deepdespeckling/sar2sar/test/model.py
rename to deepdespeckling/model.py
index 6cda078..bb3907a 100644
--- a/deepdespeckling/sar2sar/test/model.py
+++ b/deepdespeckling/model.py
@@ -1,81 +1,61 @@
-import time
-import numpy as np
-import os
-
-from merlinsar.test.utils import *
-from scipy import special
-import argparse
-
-
-# DEFINE PARAMETERS OF SPECKLE AND NORMALIZATION FACTOR
-M = 10.089038980848645
-m = -1.429329123112601
-L = 1
-c = (1 / 2) * (special.psi(L) - np.log(L))
-cn = c / (M - m)  # normalized (0,1) mean of log speckle
-
 import torch
 import numpy as np
 
 
-
-
 class Model(torch.nn.Module):
 
-    def __init__(self,height,width,device):
+    def __init__(self, device: str, height: int, width: int):
         super().__init__()
 
-        self.device=device
+        self.device = device
 
         self.height = height
         self.width = width
 
-
         self.pool = torch.nn.MaxPool2d(kernel_size=2, stride=2)
         self.leaky = torch.nn.LeakyReLU(0.1)
 
         self.enc0 = torch.nn.Conv2d(in_channels=1, out_channels=48, kernel_size=(3, 3), stride=(1, 1),
-                                    padding='same')
+                                    padding='same', device=self.device)
         self.enc1 = torch.nn.Conv2d(in_channels=48, out_channels=48, kernel_size=(3, 3), stride=(1, 1),
-                                    padding='same')
+                                    padding='same', device=self.device)
         self.enc2 = torch.nn.Conv2d(in_channels=48, out_channels=48, kernel_size=(3, 3), stride=(1, 1),
-                                    padding='same')
+                                    padding='same', device=self.device)
         self.enc3 = torch.nn.Conv2d(in_channels=48, out_channels=48, kernel_size=(3, 3), stride=(1, 1),
-                                    padding='same')
+                                    padding='same', device=self.device)
         self.enc4 = torch.nn.Conv2d(in_channels=48, out_channels=48, kernel_size=(3, 3), stride=(1, 1),
-                                    padding='same')
+                                    padding='same', device=self.device)
         self.enc5 = torch.nn.Conv2d(in_channels=48, out_channels=48, kernel_size=(3, 3), stride=(1, 1),
-                                    padding='same')
+                                    padding='same', device=self.device)
         self.enc6 = torch.nn.Conv2d(in_channels=48, out_channels=48, kernel_size=(3, 3), stride=(1, 1),
-                                    padding='same')
+                                    padding='same', device=self.device)
 
         self.dec5 = torch.nn.Conv2d(in_channels=96, out_channels=96, kernel_size=(3, 3), stride=(1, 1),
-                                    padding='same')
+                                    padding='same', device=self.device)
         self.dec5b = torch.nn.Conv2d(in_channels=96, out_channels=96, kernel_size=(3, 3), stride=(1, 1),
-                                     padding='same')
+                                     padding='same', device=self.device)
         self.dec4 = torch.nn.Conv2d(in_channels=144, out_channels=96, kernel_size=(3, 3), stride=(1, 1),
-                                    padding='same')
+                                    padding='same', device=self.device)
         self.dec4b = torch.nn.Conv2d(in_channels=96, out_channels=96, kernel_size=(3, 3), stride=(1, 1),
-                                     padding='same')
+                                     padding='same', device=self.device)
         self.dec3 = torch.nn.Conv2d(in_channels=144, out_channels=96, kernel_size=(3, 3), stride=(1, 1),
-                                    padding='same')
+                                    padding='same', device=self.device)
         self.dec3b = torch.nn.Conv2d(in_channels=96, out_channels=96, kernel_size=(3, 3), stride=(1, 1),
-                                     padding='same')
+                                     padding='same', device=self.device)
         self.dec2 = torch.nn.Conv2d(in_channels=144, out_channels=96, kernel_size=(3, 3), stride=(1, 1),
-                                    padding='same')
+                                    padding='same', device=self.device)
         self.dec2b = torch.nn.Conv2d(in_channels=96, out_channels=96, kernel_size=(3, 3), stride=(1, 1),
-                                     padding='same')
+                                     padding='same', device=self.device)
         self.dec1a = torch.nn.Conv2d(in_channels=97, out_channels=64, kernel_size=(3, 3), stride=(1, 1),
-                                     padding='same')
+                                     padding='same', device=self.device)
         self.dec1b = torch.nn.Conv2d(in_channels=64, out_channels=32, kernel_size=(3, 3), stride=(1, 1),
-                                     padding='same')
+                                     padding='same', device=self.device)
         self.dec1 = torch.nn.Conv2d(in_channels=32, out_channels=1, kernel_size=(3, 3), stride=(1, 1),
-                                    padding='same')
+                                    padding='same', device=self.device)
 
         self.upscale2d = torch.nn.UpsamplingNearest2d(scale_factor=2)
 
-
-    def forward(self,x):
+    def forward(self, x: np.array) -> np.array:
         """  Defines a class for an autoencoder algorithm for an object (image) x
 
         An autoencoder is a specific type of feedforward neural networks where the
@@ -95,7 +75,8 @@ def forward(self,x):
         a numpy array containing the denoised image i.e the image itself minus the noise
 
         """
-        x=torch.reshape(x, [1, 1, self.height, self.width])
+        x = torch.reshape(x, [1, 1, self.height, self.width])
+        # x = torch.permute(x, (0, 3, 1, 2))
         skips = [x]
 
         n = x
@@ -122,7 +103,6 @@ def forward(self,x):
         n = self.pool(n)
         n = self.leaky(self.enc6(n))
 
-
         # DECODER
         n = self.upscale2d(n)
         n = torch.cat((n, skips.pop()), dim=1)
diff --git a/deepdespeckling/sar2sar/sar2sar_denoiser.py b/deepdespeckling/sar2sar/sar2sar_denoiser.py
new file mode 100644
index 0000000..08294ba
--- /dev/null
+++ b/deepdespeckling/sar2sar/sar2sar_denoiser.py
@@ -0,0 +1,163 @@
+import logging
+import torch
+import numpy as np
+from tqdm import tqdm
+from pathlib import Path
+from glob import glob
+
+from deepdespeckling.denoiser import Denoiser
+from deepdespeckling.model import Model
+from deepdespeckling.utils.constants import M, m
+from deepdespeckling.utils.utils import (denormalize_sar_image, normalize_sar_image, save_image_to_npy_and_png,
+                                         create_empty_folder_in_directory)
+
+
+class Sar2SarDenoiser(Denoiser):
+    """Class to share parameters beyond denoising functions
+    """
+
+    def __init__(self, **params):
+        super().__init__(**params)
+
+    def load_model(self, weights_path: str, patch_size: int) -> Model:
+        """Load model with given weights 
+
+        Args:
+            weights_path (str): path to weights  
+            patch_size (int): patch size
+
+        Returns:
+            model (Model): model loaded with stored weights
+        """
+        model = Model(torch.device(self.device),
+                      height=patch_size, width=patch_size)
+        model.load_state_dict(torch.load(
+            weights_path, map_location=torch.device("cpu"))['model_state_dict'])
+
+        return model
+
+    def save_despeckled_images(self, despeckled_images: dict, image_name: str, save_dir: str):
+        """Save full, real and imaginary part of noisy and denoised image stored in a dictionary in png to a given folder
+
+        Args:
+            despeckled_images (dict): dictionary containing noisy and denoised image
+            image_name (str): name of the image
+            save_dir (str): path to the folder where to save the png images
+        """
+        threshold = np.mean(
+            despeckled_images["noisy"]) + 3 * np.std(despeckled_images["noisy"])
+        image_name = image_name.split('\\')[-1]
+
+        for key in despeckled_images:
+            create_empty_folder_in_directory(save_dir, key)
+            save_image_to_npy_and_png(
+                despeckled_images[key], save_dir, f"/{key}/{key}_", image_name, threshold)
+
+    def denoise_image_kernel(self, noisy_image_kernel: torch.tensor, denoised_image_kernel: np.array, x: int, y: int, patch_size: int, model: Model, normalisation_kernel: bool = False) -> np.array:
+        """Denoise a subpart of a given symetrised noisy image delimited by x, y and patch_size using a given model
+
+        Args:
+            noisy_image_kernel (torch tensor): part of the noisy image to denoise 
+            denoised_image_kernel (numpy array): part of the partially denoised image
+            x (int): x coordinate of current kernel to denoise
+            y (int): y coordinate of current kernel to denoise
+            patch_size (int): patch size
+            model (Model): trained model with loaded weights 
+            normalisation_kernel (bool, optional): Determine if. Defaults to False.
+
+        Returns:
+            denoised_image_kernel (numpy array): image denoised in the given coordinates and the ones already iterated
+        """
+        if not normalisation_kernel:
+
+            with torch.no_grad():
+                if self.device != 'cpu':
+                    tmp_clean_image = model.forward(
+                        noisy_image_kernel).cpu().numpy()
+                else:
+                    tmp_clean_image = model.forward(
+                        noisy_image_kernel).numpy()
+
+            tmp_clean_image = denormalize_sar_image(np.squeeze(
+                np.asarray(tmp_clean_image)))
+
+            denoised_image_kernel[x:x + patch_size, y:y + patch_size] = denoised_image_kernel[x:x +
+                                                                                              patch_size, y:y + patch_size] + tmp_clean_image
+        else:
+            denoised_image_kernel[x:x + patch_size, y:y + patch_size] = denoised_image_kernel[x:x +
+                                                                                              patch_size, y:y + patch_size] + np.ones((patch_size, patch_size))
+
+        return denoised_image_kernel
+
+    def denormalize_sar_image(self, image: np.array) -> np.array:
+        """Denormalize a sar image stored in a numpy array
+
+        Args:
+            image (numpy array): a sar image
+
+        Raises:
+            TypeError: raise an error if the image file is not a numpy array
+
+        Returns:
+            (numpy array): the image denormalized
+        """
+        if not isinstance(image, np.ndarray):
+            raise TypeError('Please provide a numpy array')
+        return np.exp((np.clip(np.squeeze(image), 0, image.max()))*(M-m)+m)
+
+    def denoise_image(self, noisy_image: np.array, weights_path: str, patch_size: int, stride_size: int) -> dict:
+        """Preprocess and denoise a coSAR image using given model weights
+
+        Args:
+            noisy_image (numpy array): numpy array containing the noisy image to despeckle 
+            weights_path (str): path to the pth model file
+            patch_size (int): size of the patch of the convolution
+            stride_size (int): number of pixels between one convolution to the next
+
+        Returns:
+            output_image (numpy array): denoised image
+        """
+        noisy_image = np.array(noisy_image).reshape(
+            1, np.size(noisy_image, 0), np.size(noisy_image, 1), 1).astype(np.float32)
+
+        noisy_image = normalize_sar_image(noisy_image)
+
+        noisy_image = torch.tensor(
+            noisy_image, dtype=torch.float)
+
+        # Pad the image
+        image_height = noisy_image.size(dim=1)
+        image_width = noisy_image.size(dim=2)
+
+        model = self.load_model(
+            weights_path=weights_path, patch_size=patch_size)
+
+        count_image = np.zeros((image_height, image_width))
+        denoised_image = np.zeros((image_height, image_width))
+
+        x_range = self.initialize_axis_range(
+            image_height, patch_size, stride_size)
+        y_range = self.initialize_axis_range(
+            image_width, patch_size, stride_size)
+
+        for x in tqdm(x_range):
+            for y in y_range:
+                noisy_image_kernel = noisy_image[:,
+                                                 x:x + patch_size, y:y + patch_size, :]
+                noisy_image_kernel = noisy_image_kernel.to(self.device)
+
+                denoised_image = self.denoise_image_kernel(
+                    noisy_image_kernel, denoised_image, x, y, patch_size, model)
+                count_image = self.denoise_image_kernel(
+                    noisy_image_kernel, count_image, x, y, patch_size, model, normalisation_kernel=True)
+
+        denoised_image = denoised_image / count_image
+
+        noisy_image_denormalized = self.denormalize_sar_image(
+            np.squeeze(np.asarray(noisy_image.cpu().numpy())))
+
+        despeckled_image = {"noisy": noisy_image_denormalized,
+                            "denoised": denoised_image
+                            }
+
+        return despeckled_image
diff --git a/deepdespeckling/sar2sar/saved_model/sar2sar.pth b/deepdespeckling/sar2sar/saved_model/sar2sar.pth
new file mode 100644
index 0000000..8d54c6c
Binary files /dev/null and b/deepdespeckling/sar2sar/saved_model/sar2sar.pth differ
diff --git a/deepdespeckling/sar2sar/train/.directory b/deepdespeckling/sar2sar/train/.directory
deleted file mode 100644
index 3fa4480..0000000
--- a/deepdespeckling/sar2sar/train/.directory
+++ /dev/null
@@ -1,4 +0,0 @@
-[Dolphin]
-Timestamp=2022,4,5,17,1,21
-Version=4
-ViewMode=1
diff --git a/deepdespeckling/sar2sar/train/Dataset.py b/deepdespeckling/sar2sar/train/Dataset.py
deleted file mode 100644
index c7148c5..0000000
--- a/deepdespeckling/sar2sar/train/Dataset.py
+++ /dev/null
@@ -1,53 +0,0 @@
-import numpy as np
-import torch
-from merlinsar.train.utils import *
-
-
-
-class Dataset(torch.utils.data.Dataset):
-    'characterizes a dataset for pytorch'
-    def __init__(self, patche):
-        self.patches = patche
-
-    def __len__(self):
-        'denotes the total number of samples'
-        return len(self.patches)
-
-    def __getitem__(self, index):
-        'Generates one sample of data'
-        #select sample
-        batch_real = (self.patches[index,:, :, 0])
-        batch_imag = (self.patches[index,:, :, 1])
-        
-        x = torch.tensor(batch_real)
-        y = torch.tensor(batch_imag)
-
-        x = x.unsqueeze(0)
-        y = y.unsqueeze(0)
-
-        return x, y
-
-
-class ValDataset(torch.utils.data.Dataset):
-    'characterizes a dataset for pytorch'
-    def __init__(self, test_set):
-        self.files = glob(test_set+'/*.npy')
-
-    def __len__(self):
-        'denotes the total number of samples'
-        return len(self.files)
-
-    def __getitem__(self, index):
-        'Generates one sample of data'
-        #select sample
-        eval_data = load_sar_images(self.files)
-
-        current_test=eval_data[index]
-
-        current_test[0,:,:,:] = symetrisation_patch(current_test[0,:,:,:])
-        image_real_part = (current_test[:, :, :, 0]).reshape(current_test.shape[0], current_test.shape[1],
-                                                              current_test.shape[2], 1)
-        image_imag_part = (current_test[:, :, :, 1]).reshape(current_test.shape[0], current_test.shape[1],
-                                                              current_test.shape[2], 1)
-
-        return torch.tensor(image_real_part).type(torch.float) , torch.tensor(image_imag_part).type(torch.float) 
diff --git a/deepdespeckling/sar2sar/train/GenerateDataset.py b/deepdespeckling/sar2sar/train/GenerateDataset.py
deleted file mode 100644
index c605613..0000000
--- a/deepdespeckling/sar2sar/train/GenerateDataset.py
+++ /dev/null
@@ -1,165 +0,0 @@
-import glob
-import random
-import os
-import numpy as np
-from scipy import signal
-from scipy import special
-
-
-'''
-Generate patches for the images in the folder dataset/data/Train
-The code scans among the training images and then for data_aug_times
-'''
-
-class GenerateDataset():
-    def data_augmentation(self, image, mode):
-        if mode == 0:
-            # original
-            return image
-        elif mode == 1:
-            # flip up and down
-            return np.flipud(image)
-        elif mode == 2:
-            # rotate counterwise 90 degree
-            return np.rot90(image)
-        elif mode == 3:
-            # rotate 90 degree and flip up and down
-            image = np.rot90(image)
-            return np.flipud(image)
-        elif mode == 4:
-            # rotate 180 degree
-            return np.rot90(image, k=2)
-        elif mode == 5:
-            # rotate 180 degree and flip
-            image = np.rot90(image, k=2)
-            return np.flipud(image)
-        elif mode == 6:
-            # rotate 270 degree
-            return np.rot90(image, k=3)
-        elif mode == 7:
-            # rotate 270 degree and flip
-            image = np.rot90(image, k=3)
-            return np.flipud(image)
-
-    def symetrisation_patch_gen(self, ima):
-        S = np.fft.fftshift(np.fft.fft2(ima[:,:,0]+1j*ima[:,:,1]))
-        p = np.zeros((S.shape[0])) # azimut (ncol)
-        for i in range(S.shape[0]):
-            p[i] = np.mean(np.abs(S[i,:]))
-        sp = p[::-1]
-        c = np.real(np.fft.ifft(np.fft.fft(p)*np.conjugate(np.fft.fft(sp))))
-        d1 = np.unravel_index(c.argmax(),p.shape[0])
-        d1 = d1[0]
-        shift_az_1 = int(round(-(d1-1)/2))%p.shape[0]+int(p.shape[0]/2)
-        p2_1 = np.roll(p,shift_az_1)
-        shift_az_2 = int(round(-(d1-1-p.shape[0])/2))%p.shape[0]+int(p.shape[0]/2)
-        p2_2 = np.roll(p,shift_az_2)
-        window = signal.gaussian(p.shape[0], std=0.2*p.shape[0])
-        test_1 = np.sum(window*p2_1)
-        test_2 = np.sum(window*p2_2)
-        # make sure the spectrum is symetrized and zeo-Doppler centered
-        if test_1>=test_2:
-            p2 = p2_1
-            shift_az = shift_az_1/p.shape[0]
-        else:
-            p2 = p2_2
-            shift_az = shift_az_2/p.shape[0]
-        S2 = np.roll(S,int(shift_az*p.shape[0]),axis=0)
-
-
-
-
-        q = np.zeros((S.shape[1])) # range (nlin)
-        for j in range(S.shape[1]):
-            q[j] = np.mean(np.abs(S[:,j]))
-        sq = q[::-1]
-        #correlation
-        cq = np.real(np.fft.ifft(np.fft.fft(q)*np.conjugate(np.fft.fft(sq))))
-        d2 = np.unravel_index(cq.argmax(),q.shape[0])
-        d2=d2[0]
-        shift_range_1 = int(round(-(d2-1)/2))%q.shape[0]+int(q.shape[0]/2)
-        q2_1 = np.roll(q,shift_range_1)
-        shift_range_2 = int(round(-(d2-1-q.shape[0])/2))%q.shape[0]+int(q.shape[0]/2)
-        q2_2 = np.roll(q,shift_range_2)
-        window_r = signal.gaussian(q.shape[0], std=0.2*q.shape[0])
-        test_1 = np.sum(window_r*q2_1)
-        test_2 = np.sum(window_r*q2_2)
-        if test_1>=test_2:
-            q2 = q2_1
-            shift_range = shift_range_1/q.shape[0]
-        else:
-            q2 = q2_2
-            shift_range = shift_range_2/q.shape[0]
-
-
-        Sf = np.roll(S2,int(shift_range*q.shape[0]),axis=1)
-        ima2 = np.fft.ifft2(np.fft.ifftshift(Sf))
-        return np.stack((np.real(ima2),np.imag(ima2)),axis=2)
-
-
-
-
-
-    def generate_patches(self,src_dir="./dataset/data/Train",pat_size=256,step=0,stride=64,bat_size=4,data_aug_times=1,n_channels=2):
-        count = 0
-        filepaths = glob.glob(src_dir + '/*.npy')
-        print("number of training data %d" % len(filepaths))
-
-        # calculate the number of patches
-        for i in range(len(filepaths)):
-            img = np.load(filepaths[i])
-
-            im_h = np.size(img, 0)
-            im_w = np.size(img, 1)
-            for x in range(0+step, (im_h - pat_size), stride):
-                for y in range(0+step, (im_w - pat_size), stride):
-                    count += 1
-        origin_patch_num = count * data_aug_times
-
-        if origin_patch_num % bat_size != 0:
-            numPatches = (origin_patch_num / bat_size + 1) * bat_size
-        else:
-            numPatches = origin_patch_num
-        print("total patches = %d , batch size = %d, total batches = %d" % \
-              (numPatches, bat_size, numPatches / bat_size))
-
-        # data matrix 4-D
-        numPatches=int(numPatches)
-        inputs = np.zeros((numPatches, pat_size, pat_size, n_channels), dtype="float32")
-
-
-        count = 0
-        # generate patches
-        for i in range(len(filepaths)): #scan through images
-            img = np.load(filepaths[i])
-            img_s = img
-
-
-            # If data_aug_times = 8 then perform them all, otherwise pick one at random or do nothing
-            for j in range(data_aug_times):
-                im_h = np.size(img, 0)
-                im_w = np.size(img, 1)
-                if data_aug_times == 8:
-                    for x in range(0 + step, im_h - pat_size, stride):
-                        for y in range(0 + step, im_w - pat_size, stride):
-                            inputs[count, :, :, :] = self.data_augmentation(img_s[x:x + pat_size, y:y + pat_size, :], \
-                                  j)
-                            count += 1
-                else:
-                    for x in range(0 + step, im_h - pat_size, stride):
-                        for y in range(0 + step, im_w - pat_size, stride):
-                            # to pick one at random, uncomment this line and comment the one below
-                            """inputs[count, :, :, :] = self.data_augmentation(img_s[x:x + pat_size, y:y + pat_size, :], \
-                                                                          random.randint(0, 7))"""
-
-                            inputs[count, :, :, :] = self.data_augmentation(self.symetrisation_patch_gen(img_s[x:x + pat_size, y:y + pat_size, :]),0)
-
-                            count += 1
-
-
-        # pad the batch
-        if count < numPatches:
-            to_pad = numPatches - count
-            inputs[-to_pad:, :, :, :] = inputs[:to_pad, :, :, :]
-
-        return inputs
diff --git a/deepdespeckling/sar2sar/train/__init__.py b/deepdespeckling/sar2sar/train/__init__.py
deleted file mode 100644
index 7f787ab..0000000
--- a/deepdespeckling/sar2sar/train/__init__.py
+++ /dev/null
@@ -1,12 +0,0 @@
-"""Top-level package for merlin."""
-
-__author__ = """youcef kemiche"""
-__email__ = 'y.kemiche06@hotmail.com'
-__version__ = '0.1.0'
-
-
-from merlinsar.train.utils import *
-from merlinsar.train.Dataset import *
-from merlinsar.train.GenerateDataset import *
-from merlinsar.train.model import *
-from merlinsar.train.merlin import*
\ No newline at end of file
diff --git a/deepdespeckling/sar2sar/train/merlin.py b/deepdespeckling/sar2sar/train/merlin.py
deleted file mode 100644
index bd3025b..0000000
--- a/deepdespeckling/sar2sar/train/merlin.py
+++ /dev/null
@@ -1,150 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-import argparse
-from glob import glob
-import os
-
-import numpy as np
-import torch
-
-from merlinsar.train.Dataset import *
-from merlinsar.train.model import *
-from merlinsar.train.utils import *
-
-
-def evaluate(model, loader):
-    outputs = [model.validation_step(batch) for batch in loader]
-    outputs = torch.tensor(outputs).T
-    loss, accuracy = torch.mean(outputs, dim=1)
-    return {"loss": loss.item(), "accuracy": accuracy.item()}
-
-
-def save_model(model,destination_folder):
-    """
-      save the ".pth" model in destination_folder
-    """
-    # Check whether the specified path exists or not
-    isExist = os.path.exists(destination_folder)
-
-    if not isExist:
-
-      # Create a new directory because it does not exist
-      os.makedirs(destination_folder)
-      print("The new directory is created!")
-
-      torch.save(model.state_dict(),destination_folder+"/model.pth")
-
-    else:
-      torch.save(model.state_dict(),destination_folder+"/model.pth")
-
-
-def fit(model,train_loader,val_loader,epochs,lr_list,eval_files,eval_set,clip_by_norm,sample_dir):
-  """ Fit the model according to the given evaluation data and parameters.
-
-  Parameters
-  ----------
-  model : model as defined in main
-  train_loader : Pytorch's DataLoader of training data
-  val_loader : Pytorch's DataLoader of validation data
-  lr_list : list of learning rates
-  eval_files : .npy files used for evaluation in training
-  eval_set : directory of dataset used for evaluation in training
-
-  Returns
-  ----------
-  self : object
-    Fitted estimator.
-
-  """
-
-  train_losses = []
-  history={}
-  epoch_num=0
-  for epoch in range(epochs):
-      epoch_num=epoch_num+1
-      print("\nEpoch", epoch+1)
-      print("***************** \n")
-      optimizer=torch.optim.Adam(model.parameters(), lr=lr_list[epoch])
-
-      #Train
-      for i, batch in enumerate(train_loader, 0):
-
-            optimizer.zero_grad()
-            loss = model.training_step(batch,i)
-            train_losses.append(loss)
-
-            loss.backward()
-
-            if(clip_by_norm==True):
-                  
-              torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=5.0)
-
-            optimizer.step()
-
-            #running_loss += loss.item()     # extract the loss value
-            print('[%d, %5d] loss: %.3f' % (epoch + 1, i + 1, loss))
-            # zero the loss
-
-          # #Validate
-      with torch.no_grad():
-        image_num=0
-        for batch in val_loader:
-            val_loss=model.validation_step(batch,image_num,epoch_num,eval_files,eval_set,sample_dir)
-            image_num=image_num+1
-
-  history["train_loss"]=train_losses
-
-  return history
-
-
-def create_model(height=256,width=256,batch_size=12,val_batch_size=1,device=torch.device("cuda:0" if torch.cuda.is_available() else "cpu"),from_pretrained=False):
-  """ Runs the denoiser algorithm for the training and evaluation dataset
-
-
-  """
-  this_dir, this_filename = os.path.split(__file__)
-  weights_path = os.path.join(this_dir, "saved_model", "model.pth")
-
-  model = Model(height,width,batch_size,val_batch_size,device)
-  model.to(device)
-  
-  if from_pretrained == True:
-        
-    model.load_state_dict(torch.load(weights_path, map_location=torch.device('cpu')))
-
-  return model
-
-def fit_model(model,lr_list,nb_epoch,training_set_directory,validation_set_directory,sample_directory,save_directory,patch_size=256,batch_size=12,val_batch_size=1,stride_size=128,n_data_augmentation=1,seed=2,clip_by_norm=True):
-  """ Runs the denoiser algorithm for the training and evaluation dataset
-
-  Parameters
-  ----------
-  model : model as defined in main
-  lr_list : list of learning rates
-
-  Returns
-  ----------
-  history : list of both training and validation loss
-
-  """
-  torch.manual_seed(seed)
-
-
-  # Prepare train DataLoader
-  train_data = load_train_data(training_set_directory,patch_size,batch_size,stride_size,n_data_augmentation) # range [0; 1]
-  train_dataset = Dataset(train_data)
-  train_loader = torch.utils.data.DataLoader(train_dataset,batch_size=batch_size,shuffle=True,drop_last=True)
-
-  # Prepare Validation DataLoader
-  eval_dataset = ValDataset(validation_set_directory)
-  eval_loader = torch.utils.data.DataLoader(eval_dataset,batch_size=val_batch_size,shuffle=False,drop_last=True)
-
-  eval_files = glob(validation_set_directory+'/*.npy')
-  # Train the model
-  history =fit(model,train_loader,eval_loader,nb_epoch,lr_list,eval_files,validation_set_directory,clip_by_norm,sample_directory)
-
-  # Save the model
-  save_model(model,save_directory)
-  print("\n model saved at :",save_directory)
-   
-  return history
diff --git a/deepdespeckling/sar2sar/train/model.py b/deepdespeckling/sar2sar/train/model.py
deleted file mode 100644
index 5f4c2bd..0000000
--- a/deepdespeckling/sar2sar/train/model.py
+++ /dev/null
@@ -1,306 +0,0 @@
-import numpy as np
-
-from merlinsar.train.utils import *
-from scipy import special
-
-
-# DEFINE PARAMETERS OF SPECKLE AND NORMALIZATION FACTOR
-M = 10.089038980848645
-m = -1.429329123112601
-L = 1
-c = (1 / 2) * (special.psi(L) - np.log(L))
-cn = c / (M - m)  # normalized (0,1) mean of log speckle
-
-import torch
-import numpy as np
-
-
-# Nearest-neighbor up-scaling layer.
-'''def upscale2d(x, factor=2):
-    """ Description
-            ----------
-            Run a nearest-neighbor up-scaling layer for a given factor on an object (image) x
-
-            This is a common quality-enhancing algorithm  that doubles the dimensions of the input.
-
-            Every pixel in low-res is thereafter transformed into 4 pixels.
-
-            Parameters
-            ----------
-            x : an image object
-
-            factor : figure by how many times ou want to multiply the input's initial dimensions
-
-            Returns
-            ----------
-            x if factor is 1
-            or
-            a reshaped x if factor is different from 1
-        """
-    assert isinstance(factor, int) and factor >= 1
-    if factor == 1:
-        return x
-    # with tf.compat.v1.variable_scope('Upscale2D'):
-    s = x.shape
-    x = torch.reshape(x, [-1, s[1], 1, s[2], 1, s[3]])
-    x = torch.tile(x, [1, 1, factor, 1, factor, 1])
-    x = torch.reshape(x, [-1, s[1] , s[2] * factor, s[3]* factor])
-    
-    
-    s = x.shape
-    x = torch.reshape(x, [-1, s[1], s[2], 1, s[3], 1])
-    x = torch.tile(x, [1, 1, 1, factor, 1, factor])
-    x = torch.reshape(x, [-1, s[1], s[2] * factor, s[3] * factor])
-
-    return x'''
-
-
-
-class Model(torch.nn.Module):
-
-    def __init__(self,height,width,batch_size,eval_batch_size,device):
-        super().__init__()
-
-        self.batch_size=batch_size
-        self.eval_batch_size=eval_batch_size
-        self.device=device
-
-        self.height = height
-        self.width = width
-
-        self.pool = torch.nn.MaxPool2d(kernel_size=2, stride=2)
-        self.leaky = torch.nn.LeakyReLU(0.1)
-
-        self.enc0 = torch.nn.Conv2d(in_channels=1, out_channels=48, kernel_size=(3, 3), stride=(1, 1),
-                                    padding='same')
-        self.enc1 = torch.nn.Conv2d(in_channels=48, out_channels=48, kernel_size=(3, 3), stride=(1, 1),
-                                    padding='same')
-        self.enc2 = torch.nn.Conv2d(in_channels=48, out_channels=48, kernel_size=(3, 3), stride=(1, 1),
-                                    padding='same')
-        self.enc3 = torch.nn.Conv2d(in_channels=48, out_channels=48, kernel_size=(3, 3), stride=(1, 1),
-                                    padding='same')
-        self.enc4 = torch.nn.Conv2d(in_channels=48, out_channels=48, kernel_size=(3, 3), stride=(1, 1),
-                                    padding='same')
-        self.enc5 = torch.nn.Conv2d(in_channels=48, out_channels=48, kernel_size=(3, 3), stride=(1, 1),
-                                    padding='same')
-        self.enc6 = torch.nn.Conv2d(in_channels=48, out_channels=48, kernel_size=(3, 3), stride=(1, 1),
-                                    padding='same')
-
-        self.dec5 = torch.nn.Conv2d(in_channels=96, out_channels=96, kernel_size=(3, 3), stride=(1, 1),
-                                    padding='same')
-        self.dec5b = torch.nn.Conv2d(in_channels=96, out_channels=96, kernel_size=(3, 3), stride=(1, 1),
-                                     padding='same')
-        self.dec4 = torch.nn.Conv2d(in_channels=144, out_channels=96, kernel_size=(3, 3), stride=(1, 1),
-                                    padding='same')
-        self.dec4b = torch.nn.Conv2d(in_channels=96, out_channels=96, kernel_size=(3, 3), stride=(1, 1),
-                                     padding='same')
-        self.dec3 = torch.nn.Conv2d(in_channels=144, out_channels=96, kernel_size=(3, 3), stride=(1, 1),
-                                    padding='same')
-        self.dec3b = torch.nn.Conv2d(in_channels=96, out_channels=96, kernel_size=(3, 3), stride=(1, 1),
-                                     padding='same')
-        self.dec2 = torch.nn.Conv2d(in_channels=144, out_channels=96, kernel_size=(3, 3), stride=(1, 1),
-                                    padding='same')
-        self.dec2b = torch.nn.Conv2d(in_channels=96, out_channels=96, kernel_size=(3, 3), stride=(1, 1),
-                                     padding='same')
-        self.dec1a = torch.nn.Conv2d(in_channels=97, out_channels=64, kernel_size=(3, 3), stride=(1, 1),
-                                     padding='same')
-        self.dec1b = torch.nn.Conv2d(in_channels=64, out_channels=32, kernel_size=(3, 3), stride=(1, 1),
-                                     padding='same')
-        self.dec1 = torch.nn.Conv2d(in_channels=32, out_channels=1, kernel_size=(3, 3), stride=(1, 1),
-                                    padding='same')
-
-        self.upscale2d = torch.nn.UpsamplingNearest2d(scale_factor=2)
-
-
-    def forward(self,x ,batch_size):
-        """  Defines a class for an autoencoder algorithm for an object (image) x
-
-        An autoencoder is a specific type of feedforward neural networks where the
-        input is the same as the
-        output. It compresses the input into a lower-dimensional code and then 
-        reconstruct the output from this representattion. It is a dimensionality 
-        reduction algorithm
-
-        Parameters
-        ----------
-        x : np.array
-        a numpy array containing image 
-
-        Returns
-        ----------
-        x-n : np.array
-        a numpy array containing the denoised image i.e the image itself minus the noise
-
-        """
-      
-        x=torch.reshape(x, [batch_size, 1, self.height, self.width])
-        skips = [x]
-
-        n = x
-
-        # ENCODER
-        n = self.leaky(self.enc0(n))
-        n = self.leaky(self.enc1(n))
-        n = self.pool(n)
-        skips.append(n)
-
-        n = self.leaky(self.enc2(n))
-        n = self.pool(n)
-        skips.append(n)
-
-        n = self.leaky(self.enc3(n))
-        n = self.pool(n)
-        skips.append(n)
-
-        n = self.leaky(self.enc4(n))
-        n = self.pool(n)
-        skips.append(n)
-
-        n = self.leaky(self.enc5(n))
-        n = self.pool(n)
-        n = self.leaky(self.enc6(n))
-
-
-        # DECODER
-        n = self.upscale2d(n)
-        n = torch.cat((n, skips.pop()), dim=1)
-        n = self.leaky(self.dec5(n))
-        n = self.leaky(self.dec5b(n))
-
-        n = self.upscale2d(n)
-        n = torch.cat((n, skips.pop()), dim=1)
-        n = self.leaky(self.dec4(n))
-        n = self.leaky(self.dec4b(n))
-
-        n = self.upscale2d(n)
-        n = torch.cat((n, skips.pop()), dim=1)
-        n = self.leaky(self.dec3(n))
-        n = self.leaky(self.dec3b(n))
-
-        n = self.upscale2d(n)
-        n = torch.cat((n, skips.pop()), dim=1)
-        n = self.leaky(self.dec2(n))
-        n = self.leaky(self.dec2b(n))
-
-        n = self.upscale2d(n)
-        n = torch.cat((n, skips.pop()), dim=1)
-        n = self.leaky(self.dec1a(n))
-        n = self.leaky(self.dec1b(n))
-
-        n = self.dec1(n)
-
-        return x - n
-
-    def loss_function(self,output,target,batch_size):
-      """ Defines and runs the loss function
-        
-      Parameters
-      ----------
-      output : 
-      target :
-      batch_size :
-
-      Returns
-      ----------
-      loss: float
-          The value of loss given your output, target and batch_size
-
-      """
-
-      M = 10.089038980848645
-      m = -1.429329123112601
- 
-      # ----- loss -----
-      log_hat_R = 2*(output*(M-m)+m)
-      hat_R = torch.exp(log_hat_R)+1e-6 # must be nonzero
-      b_square = torch.square(target)
-      loss = (1/batch_size)*torch.mean( 0.5*log_hat_R+b_square/hat_R  ) #+ tf.losses.get_regularization_loss()
-      return loss
- 
-    def training_step(self, batch,batch_number):
-      
-      """ Train the model with the training set
-
-      Parameters
-      ----------
-      batch : a subset of the training date
-      batch_number : ID identifying the batch
-
-      Returns
-      -------
-      loss : float
-        The value of loss given the batch
-        
-      """
-      M = 10.089038980848645
-      m = -1.429329123112601
-
-      x, y = batch
-      x=x.to(self.device)
-      y=y.to(self.device)
-
-
-        
-
-      if (batch_number%2==0):
-        x=(torch.log(torch.square(x)+1e-3)-2*m)/(2*(M-m))
-        out = self.forward(x,self.batch_size)
-        loss = self.loss_function(out, y,self.batch_size)
-
-      else:
-        y=(torch.log(torch.square(y)+1e-3)-2*m)/(2*(M-m))
-        out = self.forward(y,self.batch_size)
-        loss = self.loss_function(out,x,self.batch_size)
-          
-      return loss
-
-    def validation_step(self, batch,image_num,epoch_num,eval_files,eval_set,sample_dir):
-      """ Test the model with the validation set
-
-      Parameters
-      ----------
-      batch : a subset of data
-      image_num : an ID identifying the feeded image
-      epoch_num : an ID identifying the epoch
-      eval_files : .npy files used for evaluation in training
-      eval_set : directory of dataset used for evaluation in training
-
-      Returns
-      ----------
-      output_clean_image : a np.array
-            
-      """
-
-      image_real_part,image_imaginary_part = batch
-
-      image_real_part=image_real_part.to(self.device) 
-      image_imaginary_part=image_imaginary_part.to(self.device)
-
-      # Normalization
-      image_real_part_normalized=(torch.log(torch.square(image_real_part)+1e-3)-2*m)/(2*(M-m))
-      image_imaginary_part_normalized=(torch.log(torch.square(image_imaginary_part)+1e-3)-2*m)/(2*(M-m))
-            
-      out_real = self.forward(image_real_part_normalized,self.eval_batch_size)
-      out_imaginary = self.forward(image_imaginary_part_normalized,self.eval_batch_size)
-
-      output_clean_image = 0.5*(np.square(denormalize_sar(out_real.cpu().numpy()))+np.square(denormalize_sar(out_imaginary.cpu().numpy())))
-      # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # 
-
-      noisyimage = np.squeeze(np.sqrt(np.square(image_real_part.cpu().numpy())+np.square(image_imaginary_part.cpu().numpy())))
-      outputimage = np.sqrt(np.squeeze(output_clean_image))
-
-      # calculate PSNR
-      psnr = cal_psnr(outputimage, noisyimage)
-      print("img%d PSNR: %.2f" % (image_num , psnr))
-
-      # rename and save
-      imagename = eval_files[image_num].replace(eval_set, "")
-      imagename = imagename.replace('.npy', '_epoch_' + str(epoch_num) + '.npy')
-
-      save_sar_images(outputimage, noisyimage, imagename,sample_dir)
-
-      return output_clean_image
-
-
-    # def optimizer(self):
-    #   return torch.optim.Adam
\ No newline at end of file
diff --git a/deepdespeckling/sar2sar/train/sample/denoised_lely0_epoch_1.png b/deepdespeckling/sar2sar/train/sample/denoised_lely0_epoch_1.png
deleted file mode 100644
index 7766b6b..0000000
Binary files a/deepdespeckling/sar2sar/train/sample/denoised_lely0_epoch_1.png and /dev/null differ
diff --git a/deepdespeckling/sar2sar/train/utils.py b/deepdespeckling/sar2sar/train/utils.py
deleted file mode 100644
index a9fdcb7..0000000
--- a/deepdespeckling/sar2sar/train/utils.py
+++ /dev/null
@@ -1,271 +0,0 @@
-import gc
-import os
-import sys
-
-import numpy as np
-from PIL import Image
-from scipy import special
-from scipy import signal
-from glob import glob
-from merlinsar.train.GenerateDataset import GenerateDataset
-
-basedir = '.'
-
-# DEFINE PARAMETERS OF SPECKLE AND NORMALIZATION FACTOR
-M = 10.089038980848645
-m = -1.429329123112601
-L = 1
-c = (1 / 2) * (special.psi(L) - np.log(L))
-cn = c / (M - m)  # normalized (0,1) mean of log speckle
-
-
-def symetrisation_patch(ima):
-    S = np.fft.fftshift(np.fft.fft2(ima[:,:,0]+1j*ima[:,:,1]))
-    p = np.zeros((S.shape[0])) # azimut (ncol)
-    for i in range(S.shape[0]):
-        p[i] = np.mean(np.abs(S[i,:]))
-    sp = p[::-1]
-    c = np.real(np.fft.ifft(np.fft.fft(p)*np.conjugate(np.fft.fft(sp))))
-    d1 = np.unravel_index(c.argmax(),p.shape[0])
-    d1 = d1[0]
-    shift_az_1 = int(round(-(d1-1)/2))%p.shape[0]+int(p.shape[0]/2)
-    p2_1 = np.roll(p,shift_az_1)
-    shift_az_2 = int(round(-(d1-1-p.shape[0])/2))%p.shape[0]+int(p.shape[0]/2)
-    p2_2 = np.roll(p,shift_az_2)
-    window = signal.gaussian(p.shape[0], std=0.2*p.shape[0])
-    test_1 = np.sum(window*p2_1)
-    test_2 = np.sum(window*p2_2)
-    # make sure the spectrum is symetrized and zeo-Doppler centered
-    if test_1>=test_2:
-        p2 = p2_1
-        shift_az = shift_az_1/p.shape[0]
-    else:
-        p2 = p2_2
-        shift_az = shift_az_2/p.shape[0]
-    S2 = np.roll(S,int(shift_az*p.shape[0]),axis=0)
-
-    q = np.zeros((S.shape[1])) # range (nlin)
-    for j in range(S.shape[1]):
-        q[j] = np.mean(np.abs(S[:,j]))
-    sq = q[::-1]
-    #correlation
-    cq = np.real(np.fft.ifft(np.fft.fft(q)*np.conjugate(np.fft.fft(sq))))
-    d2 = np.unravel_index(cq.argmax(),q.shape[0])
-    d2=d2[0]
-    shift_range_1 = int(round(-(d2-1)/2))%q.shape[0]+int(q.shape[0]/2)
-    q2_1 = np.roll(q,shift_range_1)
-    shift_range_2 = int(round(-(d2-1-q.shape[0])/2))%q.shape[0]+int(q.shape[0]/2)
-    q2_2 = np.roll(q,shift_range_2)
-    window_r = signal.gaussian(q.shape[0], std=0.2*q.shape[0])
-    test_1 = np.sum(window_r*q2_1)
-    test_2 = np.sum(window_r*q2_2)
-    if test_1>=test_2:
-        q2 = q2_1
-        shift_range = shift_range_1/q.shape[0]
-    else:
-        q2 = q2_2
-        shift_range = shift_range_2/q.shape[0]
-
-
-    Sf = np.roll(S2,int(shift_range*q.shape[0]),axis=1)
-    ima2 = np.fft.ifft2(np.fft.ifftshift(Sf))
-    return np.stack((np.real(ima2),np.imag(ima2)),axis=2)
-
-
-def symetrisation_patch_test(real_part,imag_part):
-    S = np.fft.fftshift(np.fft.fft2(real_part[0,:,:,0]+1j*imag_part[0,:,:,0]))
-    p = np.zeros((S.shape[0])) # azimut (ncol)
-    for i in range(S.shape[0]):
-        p[i] = np.mean(np.abs(S[i,:]))
-    sp = p[::-1]
-    c = np.real(np.fft.ifft(np.fft.fft(p)*np.conjugate(np.fft.fft(sp))))
-    d1 = np.unravel_index(c.argmax(),p.shape[0])
-    d1 = d1[0]
-    shift_az_1 = int(round(-(d1-1)/2))%p.shape[0]+int(p.shape[0]/2)
-    p2_1 = np.roll(p,shift_az_1)
-    shift_az_2 = int(round(-(d1-1-p.shape[0])/2))%p.shape[0]+int(p.shape[0]/2)
-    p2_2 = np.roll(p,shift_az_2)
-    window = signal.gaussian(p.shape[0], std=0.2*p.shape[0])
-    test_1 = np.sum(window*p2_1)
-    test_2 = np.sum(window*p2_2)
-    # make sure the spectrum is symetrized and zeo-Doppler centered
-    if test_1>=test_2:
-        p2 = p2_1
-        shift_az = shift_az_1/p.shape[0]
-    else:
-        p2 = p2_2
-        shift_az = shift_az_2/p.shape[0]
-    S2 = np.roll(S,int(shift_az*p.shape[0]),axis=0)
-
-    q = np.zeros((S.shape[1])) # range (nlin)
-    for j in range(S.shape[1]):
-        q[j] = np.mean(np.abs(S[:,j]))
-    sq = q[::-1]
-    #correlation
-    cq = np.real(np.fft.ifft(np.fft.fft(q)*np.conjugate(np.fft.fft(sq))))
-    d2 = np.unravel_index(cq.argmax(),q.shape[0])
-    d2=d2[0]
-    shift_range_1 = int(round(-(d2-1)/2))%q.shape[0]+int(q.shape[0]/2)
-    q2_1 = np.roll(q,shift_range_1)
-    shift_range_2 = int(round(-(d2-1-q.shape[0])/2))%q.shape[0]+int(q.shape[0]/2)
-    q2_2 = np.roll(q,shift_range_2)
-    window_r = signal.gaussian(q.shape[0], std=0.2*q.shape[0])
-    test_1 = np.sum(window_r*q2_1)
-    test_2 = np.sum(window_r*q2_2)
-    if test_1>=test_2:
-        q2 = q2_1
-        shift_range = shift_range_1/q.shape[0]
-    else:
-        q2 = q2_2
-        shift_range = shift_range_2/q.shape[0]
-
-
-    Sf = np.roll(S2,int(shift_range*q.shape[0]),axis=1)
-    ima2 = np.fft.ifft2(np.fft.ifftshift(Sf))
-    ima2 = ima2.reshape(1, np.size(ima2, 0), np.size(ima2, 1), 1)
-    return np.real(ima2), np.imag(ima2)
-
-
-class train_data():
-    def __init__(self, filepath="%s/data/image_clean_pat.npy" % basedir):
-        self.filepath = filepath
-        assert '.npy' in filepath
-        if not os.path.exists(filepath):
-            print("[!] Data file not exists")
-            sys.exit(1)
-
-    def __enter__(self):
-        print("[*] Loading data...")
-        self.data = np.load(self.filepath)
-        np.random.shuffle(self.data)
-        print("[*] Load successfully...")
-        return self.data
-
-    def __exit__(self, type, value, trace):
-        del self.data
-        gc.collect()
-        print("In __exit__()")
-
-
-def load_data(filepath="%s/data/image_clean_pat.npy" % basedir):
-    return train_data(filepath=filepath)
-
-
-def normalize_sar(im):
-    return ((np.log(im+1e-6)-m)/(M-m)).astype(np.float32)
-
-def denormalize_sar(im):
-    return np.exp((np.clip(np.squeeze(im),0,1))*(M-m)+m)
-
-def load_train_data(filepath, patch_size, batch_size, stride_size, n_data_augmentation): #TODO: add control on training data: exit if does not exists
-    datagen = GenerateDataset()
-    imgs = datagen.generate_patches(src_dir=filepath, pat_size=patch_size, step=0,
-                             stride=stride_size, bat_size=batch_size, data_aug_times=n_data_augmentation)
-    return imgs
-
-def load_sar_images(filelist):
-    if not isinstance(filelist, list):
-        im = np.load(filelist)
-        return np.array(im).reshape(1, np.size(im, 0), np.size(im, 1), 2)
-    data = []
-    for file in filelist:
-        im = np.load(file)
-        data.append(np.array(im).reshape(1, np.size(im, 0), np.size(im, 1), 2))
-    return data
-
-
-
-def store_data_and_plot(im, threshold, filename):
-    im = np.clip(im, 0, threshold)
-    im = im / threshold * 255
-    im = Image.fromarray(im.astype('float64')).convert('L')
-    im.save(filename.replace('npy','png'))
-
-
-def save_sar_images(denoised, noisy, imagename, save_dir, groundtruth=None):
-    choices = {'marais1':190.92, 'marais2': 168.49, 'saclay':470.92, 'lely':235.90, 'ramb':167.22,
-           'risoul':306.94, 'limagne':178.43, 'saintgervais':560, 'Serreponcon': 450.0,
-          'Sendai':600.0, 'Paris': 1291.0, 'Berlin': 1036.0, 'Bergen': 553.71,
-          'SDP_Lambesc': 349.53, 'Grand_Canyon': 287.0, 'domancy': 560, 'Brazil': 103.0}
-    threshold = None
-    for x in choices:
-        if x in imagename:
-            threshold = choices.get(x)
-    if threshold is None: threshold = np.mean(noisy) + 3 * np.std(noisy)
-
-    if groundtruth:
-        groundtruthfilename = save_dir+"/groundtruth_"+imagename
-        groundtruthfilename=groundtruthfilename.replace("\\","")
-
-        np.save(groundtruthfilename,groundtruth)
-        store_data_and_plot(groundtruth, threshold, groundtruthfilename)
-
-    denoisedfilename = save_dir + "/denoised_" + imagename
-    denoisedfilename=denoisedfilename.replace("\\","")
-
-    np.save(denoisedfilename, denoised)
-    store_data_and_plot(denoised, threshold, denoisedfilename)
-
-    noisyfilename = save_dir + "/noisy_" + imagename
-    noisyfilename=noisyfilename.replace("\\","")
-
-    np.save(noisyfilename, noisy)
-    store_data_and_plot(noisy, threshold, noisyfilename)
-
-
-def save_real_imag_images(real_part, imag_part, imagename, save_dir):
-    choices = {'marais1': 190.92, 'marais2': 168.49, 'saclay': 470.92, 'lely': 235.90, 'ramb': 167.22,
-               'risoul': 306.94, 'limagne': 178.43, 'saintgervais': 560, 'Serreponcon': 450.0,
-               'Sendai': 600.0, 'Paris': 1291.0, 'Berlin': 1036.0, 'Bergen': 553.71,
-               'SDP_Lambesc': 349.53, 'Grand_Canyon': 287.0, 'Brazil': 103.0}
-    threshold = None
-    for x in choices:
-        if x in imagename:
-            threshold = choices.get(x)
-    if threshold is None: threshold = np.mean(imag_part) + 3 * np.std(imag_part)
-
-    realfilename = save_dir + "/denoised_real_" + imagename
-    realfilename=realfilename.replace("\\","")
-
-    np.save(realfilename, real_part)
-    store_data_and_plot(real_part, threshold, realfilename)
-
-    imagfilename = save_dir + "/denoised_imag_" + imagename
-    imagfilename=imagfilename.replace("\\","")
-
-    np.save(imagfilename, imag_part)
-    store_data_and_plot(imag_part, threshold, imagfilename)
-
-def save_real_imag_images_noisy(real_part, imag_part, imagename, save_dir):
-    choices = {'marais1': 190.92, 'marais2': 168.49, 'saclay': 470.92, 'lely': 235.90, 'ramb': 167.22,
-               'risoul': 306.94, 'limagne': 178.43, 'saintgervais': 560, 'Serreponcon': 450.0,
-               'Sendai': 600.0, 'Paris': 1291.0, 'Berlin': 1036.0, 'Bergen': 553.71,
-               'SDP_Lambesc': 349.53, 'Grand_Canyon': 287.0, 'Brazil': 103.0}
-    threshold = None
-    for x in choices:
-        if x in imagename:
-            threshold = choices.get(x)
-    if threshold is None: threshold = np.mean(np.abs(imag_part)) + 3 * np.std(np.abs(imag_part))
-
-    realfilename = save_dir + "/noisy_real_" + imagename
-    realfilename=realfilename.replace("\\","")
-
-    np.save(realfilename, real_part)
-    store_data_and_plot(np.sqrt(2)*np.abs(real_part), threshold, realfilename)
-
-    imagfilename = save_dir + "/noisy_imag_" + imagename
-    imagfilename=imagfilename.replace("\\","")
-    
-    np.save(imagfilename, imag_part)
-    store_data_and_plot(np.sqrt(2)*np.abs(imag_part), threshold, imagfilename)
-
-
-def cal_psnr(Shat, S):
-    # takes amplitudes in input
-    # Shat: a SAR amplitude image
-    # S:    a reference SAR image
-    P = np.quantile(S, 0.99)
-    res = 10 * np.log10((P ** 2) / np.mean(np.abs(Shat - S) ** 2))
-    return res
-
-
diff --git a/deepdespeckling/utils/__init__.py b/deepdespeckling/utils/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/deepdespeckling/utils/constants.py b/deepdespeckling/utils/constants.py
new file mode 100644
index 0000000..0adb93c
--- /dev/null
+++ b/deepdespeckling/utils/constants.py
@@ -0,0 +1,11 @@
+# Global maximum and minimum values obtained empirically and used to normalize SAR images
+M = 10.089038980848645
+m = -1.429329123112601
+
+# Must be a power of 2 lower than min(height, width) of the image to despeckle
+# Default to 256 as the trained spotlight and stripmap models stored in merlin/saved_model
+PATCH_SIZE = 256
+
+# Has to be lower than the PATCH_SIZE
+# Default to PATCH_SIZE - 2 in order to not have visible borders in the despeckled images
+STRIDE_SIZE = PATCH_SIZE - 2
diff --git a/deepdespeckling/merlin/test/load_cosar.py b/deepdespeckling/utils/load_cosar.py
similarity index 54%
rename from deepdespeckling/merlin/test/load_cosar.py
rename to deepdespeckling/utils/load_cosar.py
index f3da7d9..1e72ddf 100644
--- a/deepdespeckling/merlin/test/load_cosar.py
+++ b/deepdespeckling/utils/load_cosar.py
@@ -1,13 +1,24 @@
 import struct
+from osgeo import gdal
 import numpy as np
 
-def cos2mat(imgName):
+
+def cos2mat(path_to_cosar_image: str) -> np.array:
+    """Convert a CoSAR imge to a numpy array of size [ncolumns,nlines,2]
+
+    Args:
+        path_to_cosar_image (str): path to the image which is a cos file
+
+    Returns:
+        numpy array : the image in a numpy array
+    """
+
     print('Converting CoSAR to numpy array of size [ncolumns,nlines,2]')
 
     try:
-        fin = open(imgName, 'rb');
+        fin = open(path_to_cosar_image, 'rb')
     except IOError:
-        legx = imgName + ': it is a not openable file'
+        legx = path_to_cosar_image + ': it is a not openable file'
         print(legx)
         print(u'failed to call cos2mat')
         return 0, 0, 0, 0
@@ -35,12 +46,32 @@ def cos2mat(imgName):
     firm = fin.read(4 * ncoltot)
     firm = fin.read(4 * ncoltot)
     firm = fin.read(4 * ncoltot)
-    #
+
     for iut in range(nlig):
         firm = fin.read(4 * ncoltot)
         imgligne = np.ndarray(2 * ncoltot, '>h', firm)
-        imgcxs[iut, :] = imgligne[4:2 * ncoltot:2] + 1j * imgligne[5:2 * ncoltot:2]
+        imgcxs[iut, :] = imgligne[4:2 * ncoltot:2] + \
+            1j * imgligne[5:2 * ncoltot:2]
 
     print('[:,:,0] contains the real part of the SLC image data')
     print('[:,:,1] contains the imaginary part of the SLC image data')
-    return np.stack((np.real(imgcxs), np.imag(imgcxs)), axis=2)
\ No newline at end of file
+
+    return np.stack((np.real(imgcxs), np.imag(imgcxs)), axis=2)
+
+
+def load_tiff_image(path_to_tiff_image: str) -> np.array:
+    """Load a tiff image in a numpy array using gdal library
+
+    Args:
+        path_to_tiff_image (str): path to a tiff image
+
+    Returns:
+        (numpy array): numpy array containing the tiff image
+    """
+    dataset = gdal.Open(path_to_tiff_image)
+
+    for x in range(1, dataset.RasterCount + 1):
+        band = dataset.GetRasterBand(x)
+        array = band.ReadAsArray()
+
+    return array.astype(np.float64)
diff --git a/deepdespeckling/utils/utils.py b/deepdespeckling/utils/utils.py
new file mode 100644
index 0000000..b7d0a68
--- /dev/null
+++ b/deepdespeckling/utils/utils.py
@@ -0,0 +1,575 @@
+import numpy as np
+import cv2
+import os
+from PIL import Image
+from scipy import signal
+from pathlib import Path
+from glob import glob
+
+from deepdespeckling.utils.load_cosar import cos2mat, load_tiff_image
+from deepdespeckling.utils.constants import M, m
+
+
+def normalize_sar_image(image: np.array) -> np.array:
+    """normalize a sar image store in  a numpy array
+
+    Args:
+        image (numpy array): the image to be normalized
+
+    Returns:
+        (numpy array): normalized image
+    """
+    return ((np.log(np.clip(image, 0, image.max())+1e-6)-m)/(M-m)).astype(np.float32)
+
+
+def denormalize_sar_image(image: np.array) -> np.array:
+    """Denormalize a sar image store in  a numpy array
+
+    Args:
+        image (numpy array): a sar image
+
+    Raises:
+        TypeError: raise an error if the image file is not a numpy array
+
+    Returns:
+        (numpy array): the image denormalized
+    """
+    if not isinstance(image, np.ndarray):
+        raise TypeError('Please provide a numpy array')
+    return np.exp((M - m) * (np.squeeze(image)).astype('float32') + m)
+
+
+def denormalize_sar_image_sar2sar(image: np.array) -> np.array:
+    """Denormalize a sar image store i  a numpy array
+
+    Args:
+        image (numpy array): a sar image
+
+    Raises:
+        TypeError: raise an error if the image file is not a numpy array
+
+    Returns:
+        (numpy array): the image denormalized
+    """
+    if not isinstance(image, np.ndarray):
+        raise TypeError('Please provide a numpy array')
+    return np.exp((np.clip(np.squeeze(image), 0, image.max()))*(M-m)+m)
+
+
+def load_sar_image(image_path: str) -> np.array:
+    """Load a SAR image in a numpy array, use cos2mat function if the file is a cos file, 
+    load_tiff_image if the file is a tiff file
+
+    Args:
+        image_path (str) : absolute path to a SAR image (cos or npy file)
+
+    Returns:
+        image (numpy array) : the image of dimension [ncolumns,nlines,2]
+    """
+    if Path(image_path).suffix == ".npy":
+        image = np.load(image_path)
+    elif Path(image_path).suffix == ".cos":
+        image = cos2mat(image_path)
+    elif Path(image_path).suffix == ".tiff":
+        image = load_tiff_image(image_path)
+    else:
+        raise ValueError("the image should be a cos, npy or tiff file")
+    return image
+
+
+def load_sar_images(file_list):
+    """ Description
+            ----------
+            Loads files , resize them and append them into a list called data
+
+            Parameters
+            ----------
+            filelist : a path to a folder containing the images
+
+            Returns
+            ----------
+            A list of images
+
+        """
+    if not isinstance(file_list, list):
+        image = np.load(file_list)
+        image = np.array(image).reshape(
+            1, np.size(image, 0), np.size(image, 1), 2)
+        return image
+    data = []
+    for file in file_list:
+        image = np.load(file)
+        data.append(np.array(image).reshape(
+            1, np.size(image, 0), np.size(image, 1), 2))
+    return data
+
+
+def create_empty_folder_in_directory(destination_directory_path: str, folder_name: str = "processed_images") -> str:
+    """Create an empty folder in a given directory
+
+    Args:
+        destination_directory_path (str): path pf the directory in which an empty folder is created if it doest not exist yet
+        folder_name (str, optional): name of the folder to create. Defaults to "processed_images".
+
+    Returns:
+        processed_images_path: path of the created empty folder
+    """
+    processed_images_path = destination_directory_path + f'/{folder_name}'
+    if not os.path.exists(processed_images_path):
+        os.mkdir(processed_images_path)
+    return processed_images_path
+
+
+def preprocess_and_store_sar_images(sar_images_path: str, processed_images_path: str, model_name: str = "spotlight"):
+    """Convert coSAR images to numpy arrays and store it in a specified path
+
+    Args:
+        sar_images_path (str): path of a folder containing coSAR images to be converted in numpy array
+        processed_images_path (str): path of the folder where converted images are stored
+        model_name (str): model name to be use for despeckling 
+    """
+    ext = "cos" if model_name in ["spotlight", "stripmap"] else "tiff"
+    images_paths = glob(os.path.join(sar_images_path, "*.npy")) + \
+        glob(os.path.join(sar_images_path, f"*.{ext}"))
+    for image_path in images_paths:
+        imagename = image_path.split('/')[-1].split('.')[0]
+        if not os.path.exists(processed_images_path + '/' + imagename + '.npy'):
+            image = load_sar_image(image_path)
+            np.save(processed_images_path + '/' + imagename + '.npy', image)
+
+
+def preprocess_and_store_sar_images_from_coordinates(sar_images_path: str, processed_images_path: str, coordinates_dict: dict, model_name: str = "spotlight"):
+    """Convert specified areas of coSAR images to numpy arrays and store it in a specified path
+
+    Args:
+        sar_images_path (str): path of a folder containing coSAR images to be converted in numpy array
+        processed_images_path (str): path of the folder where converted images are stored
+        coordinates_dict (dict): dictionary containing pixel boundaries of the area to despeckle (x_start, x_end, y_start, y_end)
+        model_name (str): model name to be use for despeckling. Default to "spotlight"
+    """
+    x_start = coordinates_dict["x_start"]
+    x_end = coordinates_dict["x_end"]
+    y_start = coordinates_dict["y_start"]
+    y_end = coordinates_dict["y_end"]
+
+    ext = "cos" if model_name in ["spotlight", "stripmap"] else "tiff"
+
+    images_paths = glob(os.path.join(sar_images_path, "*.npy")) + \
+        glob(os.path.join(sar_images_path, f"*.{ext}"))
+    for image_path in images_paths:
+        imagename = image_path.split('/')[-1].split('.')[0]
+        if not os.path.exists(processed_images_path + '/' + imagename + '.npy'):
+            image = load_sar_image(image_path)
+            if ext == "cos":
+                np.save(processed_images_path + '/' + imagename +
+                        '.npy', image[x_start:x_end, y_start:y_end, :])
+            else:
+                np.save(processed_images_path + '/' + imagename +
+                        '.npy', image[x_start:x_end, y_start:y_end])
+
+
+def get_maximum_patch_size(kernel_size: int, patch_bound: int) -> int:
+    """Get maximum manifold of a number lower than a bound
+
+    Args:
+        kernel_size (int): the kernel size of the trained model
+        patch_bound (int): the maximum bound of the kernel size
+
+    Returns:
+        maximum_patch_size (int) : the maximum patch size
+    """
+    k = 1
+
+    while kernel_size * k < patch_bound:
+        k = k * 2
+
+    maximum_patch_size = int(kernel_size * (k/2))
+
+    return maximum_patch_size
+
+
+def get_maximum_patch_size_from_image_dimensions(kernel_size: int, height: int, width: int) -> int:
+    """Get the maximum patch size from the width and heigth and the kernel size of the model we use
+
+    Args:
+        kernel_size (int): the kernel size of the trained model
+        height (int): the heigth of the image
+        width (int): the width of the image
+
+    Returns:
+        maximum_patch_size (int) : the maximum patch size to use for despeckling
+    """
+    patch_bound = min(height, width)
+
+    if patch_bound <= kernel_size:
+        maximum_patch_size = kernel_size
+    else:
+        maximum_patch_size = get_maximum_patch_size(
+            kernel_size=kernel_size, patch_bound=patch_bound)
+
+    return maximum_patch_size
+
+
+def symetrise_real_and_imaginary_parts(real_part: np.array, imag_part: np.array) -> (np.array, np.array):
+    """Symetrise given real and imaginary parts to ensure MERLIN properties
+
+    Args:
+        real_part (numpy array): real part of the noisy image to symetrise
+        imag_part (numpy array): imaginary part of the noisy image to symetrise 
+
+    Returns:
+        np.real(ima2), np.imag(ima2) (numpy array, numpy array): symetrised real and imaginary parts of a noisy image
+    """
+    S = np.fft.fftshift(np.fft.fft2(
+        real_part[0, :, :, 0] + 1j * imag_part[0, :, :, 0]))
+    p = np.zeros((S.shape[0]))  # azimut (ncol)
+    for i in range(S.shape[0]):
+        p[i] = np.mean(np.abs(S[i, :]))
+    sp = p[::-1]
+    c = np.real(np.fft.ifft(np.fft.fft(p) * np.conjugate(np.fft.fft(sp))))
+    d1 = np.unravel_index(c.argmax(), p.shape[0])
+    d1 = d1[0]
+    shift_az_1 = int(round(-(d1 - 1) / 2)) % p.shape[0] + int(p.shape[0] / 2)
+    p2_1 = np.roll(p, shift_az_1)
+    shift_az_2 = int(
+        round(-(d1 - 1 - p.shape[0]) / 2)) % p.shape[0] + int(p.shape[0] / 2)
+    p2_2 = np.roll(p, shift_az_2)
+    window = signal.gaussian(p.shape[0], std=0.2 * p.shape[0])
+    test_1 = np.sum(window * p2_1)
+    test_2 = np.sum(window * p2_2)
+    # make sure the spectrum is symetrized and zeo-Doppler centered
+    if test_1 >= test_2:
+        p2 = p2_1
+        shift_az = shift_az_1 / p.shape[0]
+    else:
+        p2 = p2_2
+        shift_az = shift_az_2 / p.shape[0]
+    S2 = np.roll(S, int(shift_az * p.shape[0]), axis=0)
+
+    q = np.zeros((S.shape[1]))  # range (nlin)
+    for j in range(S.shape[1]):
+        q[j] = np.mean(np.abs(S[:, j]))
+    sq = q[::-1]
+    # correlation
+    cq = np.real(np.fft.ifft(np.fft.fft(q) * np.conjugate(np.fft.fft(sq))))
+    d2 = np.unravel_index(cq.argmax(), q.shape[0])
+    d2 = d2[0]
+    shift_range_1 = int(round(-(d2 - 1) / 2)
+                        ) % q.shape[0] + int(q.shape[0] / 2)
+    q2_1 = np.roll(q, shift_range_1)
+    shift_range_2 = int(
+        round(-(d2 - 1 - q.shape[0]) / 2)) % q.shape[0] + int(q.shape[0] / 2)
+    q2_2 = np.roll(q, shift_range_2)
+    window_r = signal.gaussian(q.shape[0], std=0.2 * q.shape[0])
+    test_1 = np.sum(window_r * q2_1)
+    test_2 = np.sum(window_r * q2_2)
+    if test_1 >= test_2:
+        q2 = q2_1
+        shift_range = shift_range_1 / q.shape[0]
+    else:
+        q2 = q2_2
+        shift_range = shift_range_2 / q.shape[0]
+
+    Sf = np.roll(S2, int(shift_range * q.shape[0]), axis=1)
+    ima2 = np.fft.ifft2(np.fft.ifftshift(Sf))
+
+    return np.real(ima2), np.imag(ima2)
+
+
+def preprocess_image(image: np.array, threshold: float) -> np.array:
+    """Preprocess image by limiting pixel values with a threshold
+
+    Args:
+        image (numpy array): image to preprocess
+        threshold (float): pixel value threshold 
+
+    Returns:
+        image (cv2 Image): image to be saved
+    """
+    image = np.clip(image, 0, threshold)
+    image = image / threshold * 255
+    image = Image.fromarray(image.astype('float64')).convert('L')
+
+    return image
+
+
+def save_image_to_png(image: np.array, threshold: int, image_full_path: str):
+    """Save a given image to a png file in a given folder
+
+    Args:
+        image (numpy array): image to save 
+        threshold (float): threshold of pixel values of the image to be saved in png
+        image_full_path (str): full path of the image
+
+    Raises:
+        TypeError: if the image is not a numpy array
+    """
+    if not isinstance(image, np.ndarray):
+        raise TypeError('Please provide a numpy array')
+
+    image = preprocess_image(image, threshold=threshold)
+    image.save(image_full_path.replace('npy', 'png'))
+
+
+def save_image_to_npy_and_png(image: np.array, save_dir: str, prefix: str, image_name: str, threshold: float):
+    """Save a given image to npy and png in a given folder
+
+    Args:
+        image (numpy array): image to save
+        save_dir (str): path to the folder where to save the image
+        prefix (str): prefix of the image file name
+        image_name (str): name of the image file
+        threshold (float): threshold of image pixel values used for png conversion
+    """
+    image_full_path = save_dir + prefix + image_name
+
+    # Save image to npy file
+    np.save(image_full_path, image)
+
+    # Save image to png file
+    save_image_to_png(image, threshold, image_full_path)
+
+
+def compute_psnr(Shat: np.array, S: np.array) -> float:
+    """Compute Peak Signal to Noise Ratio
+
+    Args:
+        Shat (numpy array): a SAR amplitude image
+        S (numpy array): a reference SAR image
+
+    Returns:
+        res (float): psnr value
+    """
+    P = np.quantile(S, 0.99)
+    res = 10 * np.log10((P ** 2) / np.mean(np.abs(Shat - S) ** 2))
+    return res
+
+
+def get_cropping_coordinates(image: np.array, destination_directory_path: str, model_name: str, fixed: bool = True):
+    """Launch the crop tool to enable the user to select the subpart of the image to be despeckled
+
+    Args:
+        image (numpy aray): full image to be cropped 
+        destination_directory_path (str): path of a folder to store the results
+        model_name (str): model name to be use for despeckling. Default to "spotlight"
+        fixed (bool, optional): whether the area of selection has a fixed size of not. Defaults to True.
+    """
+    image = preprocess_sar_image_for_cropping(image, model_name)
+    full_image = image.copy()
+    cropping = False
+    x_start, y_start, x_end, y_end = 0, 0, 0, 0
+
+    # CV2 CROPPING IN WINDOW
+    def mouse_crop(event, x, y, flags, param):
+        """ The callback function of crop() to deal with user's events
+        """
+        global x_start, y_start, x_end, y_end, cropping
+        cropping = False
+
+        if event == cv2.EVENT_LBUTTONDOWN:
+            x_start, y_start, x_end, y_end = x, y, x, y
+            cropping = True
+
+        # Mouse is Moving
+        elif event == cv2.EVENT_MOUSEMOVE:
+            x_end, y_end = x, y
+
+        # if the left mouse button was released
+        elif event == cv2.EVENT_LBUTTONUP:
+            # record the ending (x, y) coordinates
+            x_end, y_end = x, y
+            if fixed:
+                if x_start > x_end and y_start > y_end:
+                    tempxstart = x_start
+                    tempystart = y_start
+
+                    x_start = tempxstart - 32
+                    x_end = tempxstart
+
+                    y_start = tempystart - 32
+                    y_end = tempystart
+
+                elif x_start > x_end and y_start < y_end:
+                    tempxstart = x_start
+                    tempystart = y_start
+
+                    x_start = tempxstart - 32
+                    y_start = tempystart
+
+                    x_end = tempxstart
+                    y_end = tempystart + 32
+
+                elif x_start < x_end and y_start > y_end:
+                    tempxstart = x_start
+                    tempystart = y_start
+
+                    x_start = tempxstart
+                    y_start = tempystart - 32
+                    x_end = tempxstart + 32
+                    y_end = tempystart
+
+                else:
+                    x_end = x_start + 32
+                    y_end = y_start + 32
+            else:
+                if x_start > x_end and y_start > y_end:
+                    tempx = x_start
+                    x_start = x_end
+                    x_end = tempx
+
+                    tempy = y_start
+                    y_start = y_end
+                    y_end = tempy
+
+                elif x_start > x_end and y_start < y_end:
+                    tempxstart = x_start
+                    tempystart = y_start
+                    tempxend = x_end
+                    tempyend = y_end
+
+                    x_start = tempxend
+                    y_start = tempystart
+                    x_end = tempxstart
+                    y_end = tempyend
+
+                elif x_start < x_end and y_start > y_end:
+                    tempxstart = x_start
+                    tempystart = y_start
+                    tempxend = x_end
+                    tempyend = y_end
+
+                    x_start = tempxstart
+                    y_start = tempyend
+                    x_end = tempxend
+                    y_end = tempystart
+
+            # cropping is finished
+            cv2.rectangle(image, (x_start, y_start),
+                          (x_end, y_end), (255, 0, 0), 2)
+            cropping = False
+
+            refPoint = [(x_start, y_start), (x_end, y_end)]
+
+            if len(refPoint) == 2:  # when two points were found
+                cropped_image = full_image[refPoint[0][1] * 8:refPoint[1][1]
+                                           * 8, refPoint[0][0] * 8:refPoint[1][0] * 8]
+                if fixed:
+                    cropped_image = cv2.resize(cropped_image, (256, 256))
+                else:
+                    cropped_image = cv2.resize(
+                        cropped_image, (8 * (x_end - x_start), 8 * (y_end - y_start)))
+                cv2.imshow("Cropped", cropped_image)
+
+                with open(destination_directory_path+'/cropping_coordinates.txt', 'w') as filehandle:
+                    for listitem in refPoint:
+                        filehandle.write(f'{listitem}\n')
+
+    h, w, _ = image.shape
+    # resizing image
+    image = cv2.resize(image, (int(w / 8), int(h / 8)))
+    cv2.namedWindow("image")
+    cv2.setMouseCallback("image", mouse_crop)
+
+    while True:
+        i = image.copy()
+        if not cropping:
+            cv2.imshow("image", image)
+        elif cropping:
+            cv2.imshow("image", i)
+            if not fixed:
+                cv2.rectangle(i, (x_start, y_start),
+                              (x_end, y_end), (255, 0, 0), 2)
+        key = cv2.waitKey(10)
+        if key == ord('q'):
+            cv2.destroyAllWindows()
+            return get_cropping_coordinates_from_file(destination_directory_path=destination_directory_path)
+
+
+def get_cropping_coordinates_from_file(destination_directory_path: str) -> list:
+    """Get cropping coordinates from a file where it's stored
+
+    Args:
+        destination_directory_path (str): path of the file in which the cropping coordinates are stored
+
+    Returns:
+        cropping_coordinates (list): list containing cropping coordinates
+    """
+    cropping_coordinates = []
+
+    with open(destination_directory_path+'/cropping_coordinates.txt', 'r') as filehandle:
+        for line in filehandle:
+            # Remove linebreak which is the last character of the string
+            curr_place = eval(line[:-1])
+            # Add item to the list
+            cropping_coordinates.append(curr_place)
+
+    return cropping_coordinates
+
+
+def crop_image(image: np.array, image_path: str, cropping_coordinates: list, model_name: str, processed_images_path: str):
+    """Crop an image using given cropping coordinates and store the result in a given folder
+
+    Args:
+        image (numpy array): image to be cropped
+        image_path (str): path of the image
+        cropping_coordinates (list): list of coordinates of cropping, format [(x1, y1), (x2, y2)]
+        model_name (str): name of the model (stripmap, spotlight or sar2sar)
+        processed_images_path (str): path of the folder where to store the cropped image in npy format
+    """
+    if model_name in ["spotlight", "stripmap"]:
+        image_real_part = image[:, :, 0]
+        image_imaginary_part = image[:, :, 1]
+
+        cropped_image_real_part = image_real_part[cropping_coordinates[0][1] * 8:cropping_coordinates[1][1] * 8,
+                                                  cropping_coordinates[0][0] * 8:cropping_coordinates[1][0] * 8]
+        cropped_image_imaginary_part = image_imaginary_part[cropping_coordinates[0][1] * 8:cropping_coordinates[1][1] * 8,
+                                                            cropping_coordinates[0][0] * 8:cropping_coordinates[1][0] * 8]
+
+        cropped_image_real_part = cropped_image_real_part.reshape(cropped_image_real_part.shape[0],
+                                                                  cropped_image_real_part.shape[1], 1)
+        cropped_image_imaginary_part = cropped_image_imaginary_part.reshape(cropped_image_imaginary_part.shape[0],
+                                                                            cropped_image_imaginary_part.shape[1], 1)
+
+        cropped_image = np.concatenate(
+            (cropped_image_real_part, cropped_image_imaginary_part), axis=2)
+    else:
+        cropped_image = image[cropping_coordinates[0][1] * 8:cropping_coordinates[1][1] * 8,
+                              cropping_coordinates[0][0] * 8:cropping_coordinates[1][0] * 8]
+
+        cropped_image = cropped_image.reshape(
+            cropped_image.shape[0], cropped_image.shape[1], 1)
+
+    image_path_name = Path(image_path)
+    np.save(processed_images_path + '/' + image_path_name.stem +
+            '_cropped_to_denoise', cropped_image)
+
+
+def preprocess_sar_image_for_cropping(image: np.array, model_name: str) -> np.array:
+    """Preprocess image to use the cropping tool
+
+    Args:
+        image (numpy array): image from which we get cropping coordinates by using the cropping tool
+        model_name (str): name of the model (stripmap, spotlight or sar2sar)
+
+    Returns:
+        image (cv2 image): image preprocessed for cropping
+    """
+    if model_name in ["spotlight", "stripmap"]:
+        image_data_real = image[:, :, 0]
+        image_data_imag = image[:, :, 1]
+        image = np.squeeze(
+            np.sqrt(np.square(image_data_real) + np.square(image_data_imag)))
+
+    threshold = np.mean(image) + 3 * np.std(image)
+
+    image = np.clip(image, 0, threshold)
+    image = image / threshold * 255
+
+    image = Image.fromarray(image.astype('float64')).convert('L')
+    image = cv2.cvtColor(np.array(image), cv2.COLOR_RGB2BGR)
+
+    return image
+
+
+if __name__ == "__main__":
+    print(M)
diff --git a/docs/Makefile b/docs/Makefile
deleted file mode 100644
index bf630ee..0000000
--- a/docs/Makefile
+++ /dev/null
@@ -1,20 +0,0 @@
-# Minimal makefile for Sphinx documentation
-#
-
-# You can set these variables from the command line.
-SPHINXOPTS    =
-SPHINXBUILD   = python -msphinx
-SPHINXPROJ    = deepdespeckling
-SOURCEDIR     = .
-BUILDDIR      = _build
-
-# Put it first so that "make" without argument is like "make help".
-help:
-	@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
-
-.PHONY: help Makefile
-
-# Catch-all target: route all unknown targets to Sphinx using the new
-# "make mode" option.  $(O) is meant as a shortcut for $(SPHINXOPTS).
-%: Makefile
-	@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
diff --git a/docs/authors.rst b/docs/authors.rst
deleted file mode 100644
index e122f91..0000000
--- a/docs/authors.rst
+++ /dev/null
@@ -1,1 +0,0 @@
-.. include:: ../AUTHORS.rst
diff --git a/docs/conf.py b/docs/conf.py
deleted file mode 100644
index 0f79b23..0000000
--- a/docs/conf.py
+++ /dev/null
@@ -1,162 +0,0 @@
-#!/usr/bin/env python
-#
-# deepdespeckling documentation build configuration file, created by
-# sphinx-quickstart on Fri Jun  9 13:47:02 2017.
-#
-# This file is execfile()d with the current directory set to its
-# containing dir.
-#
-# Note that not all possible configuration values are present in this
-# autogenerated file.
-#
-# All configuration values have a default; values that are commented out
-# serve to show the default.
-
-# If extensions (or modules to document with autodoc) are in another
-# directory, add these directories to sys.path here. If the directory is
-# relative to the documentation root, use os.path.abspath to make it
-# absolute, like shown here.
-#
-import os
-import sys
-sys.path.insert(0, os.path.abspath('..'))
-
-import deepdespeckling
-
-# -- General configuration ---------------------------------------------
-
-# If your documentation needs a minimal Sphinx version, state it here.
-#
-# needs_sphinx = '1.0'
-
-# Add any Sphinx extension module names here, as strings. They can be
-# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
-extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode']
-
-# Add any paths that contain templates here, relative to this directory.
-templates_path = ['_templates']
-
-# The suffix(es) of source filenames.
-# You can specify multiple suffix as a list of string:
-#
-# source_suffix = ['.rst', '.md']
-source_suffix = '.rst'
-
-# The master toctree document.
-master_doc = 'index'
-
-# General information about the project.
-project = 'deepdespeckling'
-copyright = "2022, youcef kemiche"
-author = "youcef kemiche"
-
-# The version info for the project you're documenting, acts as replacement
-# for |version| and |release|, also used in various other places throughout
-# the built documents.
-#
-# The short X.Y version.
-version = deepdespeckling.__version__
-# The full version, including alpha/beta/rc tags.
-release = deepdespeckling.__version__
-
-# The language for content autogenerated by Sphinx. Refer to documentation
-# for a list of supported languages.
-#
-# This is also used if you do content translation via gettext catalogs.
-# Usually you set "language" from the command line for these cases.
-language = None
-
-# List of patterns, relative to source directory, that match files and
-# directories to ignore when looking for source files.
-# This patterns also effect to html_static_path and html_extra_path
-exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
-
-# The name of the Pygments (syntax highlighting) style to use.
-pygments_style = 'sphinx'
-
-# If true, `todo` and `todoList` produce output, else they produce nothing.
-todo_include_todos = False
-
-
-# -- Options for HTML output -------------------------------------------
-
-# The theme to use for HTML and HTML Help pages.  See the documentation for
-# a list of builtin themes.
-#
-html_theme = 'alabaster'
-
-# Theme options are theme-specific and customize the look and feel of a
-# theme further.  For a list of options available for each theme, see the
-# documentation.
-#
-# html_theme_options = {}
-
-# Add any paths that contain custom static files (such as style sheets) here,
-# relative to this directory. They are copied after the builtin static files,
-# so a file named "default.css" will overwrite the builtin "default.css".
-html_static_path = ['_static']
-
-
-# -- Options for HTMLHelp output ---------------------------------------
-
-# Output file base name for HTML help builder.
-htmlhelp_basename = 'deepdespecklingdoc'
-
-
-# -- Options for LaTeX output ------------------------------------------
-
-latex_elements = {
-    # The paper size ('letterpaper' or 'a4paper').
-    #
-    # 'papersize': 'letterpaper',
-
-    # The font size ('10pt', '11pt' or '12pt').
-    #
-    # 'pointsize': '10pt',
-
-    # Additional stuff for the LaTeX preamble.
-    #
-    # 'preamble': '',
-
-    # Latex figure (float) alignment
-    #
-    # 'figure_align': 'htbp',
-}
-
-# Grouping the document tree into LaTeX files. List of tuples
-# (source start file, target name, title, author, documentclass
-# [howto, manual, or own class]).
-latex_documents = [
-    (master_doc, 'deepdespeckling.tex',
-     'deepdespeckling Documentation',
-     'youcef kemiche', 'manual'),
-]
-
-
-# -- Options for manual page output ------------------------------------
-
-# One entry per manual page. List of tuples
-# (source start file, name, description, authors, manual section).
-man_pages = [
-    (master_doc, 'deepdespeckling',
-     'deepdespeckling Documentation',
-     [author], 1)
-]
-
-
-# -- Options for Texinfo output ----------------------------------------
-
-# Grouping the document tree into Texinfo files. List of tuples
-# (source start file, target name, title, author,
-#  dir menu entry, description, category)
-texinfo_documents = [
-    (master_doc, 'deepdespeckling',
-     'deepdespeckling Documentation',
-     author,
-     'deepdespeckling',
-     'One line description of project.',
-     'Miscellaneous'),
-]
-
-
-
diff --git a/docs/contributing.rst b/docs/contributing.rst
deleted file mode 100644
index e582053..0000000
--- a/docs/contributing.rst
+++ /dev/null
@@ -1,1 +0,0 @@
-.. include:: ../CONTRIBUTING.rst
diff --git a/docs/history.rst b/docs/history.rst
deleted file mode 100644
index 2506499..0000000
--- a/docs/history.rst
+++ /dev/null
@@ -1,1 +0,0 @@
-.. include:: ../HISTORY.rst
diff --git a/docs/index.rst b/docs/index.rst
deleted file mode 100644
index 5a14e24..0000000
--- a/docs/index.rst
+++ /dev/null
@@ -1,20 +0,0 @@
-Welcome to deepdespeckling's documentation!
-======================================
-
-.. toctree::
-   :maxdepth: 2
-   :caption: Contents:
-
-   readme
-   installation
-   usage
-   modules
-   contributing
-   authors
-   history
-
-Indices and tables
-==================
-* :ref:`genindex`
-* :ref:`modindex`
-* :ref:`search`
diff --git a/docs/installation.rst b/docs/installation.rst
deleted file mode 100644
index 5ff1d16..0000000
--- a/docs/installation.rst
+++ /dev/null
@@ -1,51 +0,0 @@
-.. highlight:: shell
-
-============
-Installation
-============
-
-
-Stable release
---------------
-
-To install deepdespeckling, run this command in your terminal:
-
-.. code-block:: console
-
-    $ pip install deepdespeckling
-
-This is the preferred method to install deepdespeckling, as it will always install the most recent stable release.
-
-If you don't have `pip`_ installed, this `Python installation guide`_ can guide
-you through the process.
-
-.. _pip: https://pip.pypa.io
-.. _Python installation guide: http://docs.python-guide.org/en/latest/starting/installation/
-
-
-From sources
-------------
-
-The sources for deepdespeckling can be downloaded from the `Github repo`_.
-
-You can either clone the public repository:
-
-.. code-block:: console
-
-    $ git clone git://github.com/ykemiche/deepdespeckling
-
-Or download the `tarball`_:
-
-.. code-block:: console
-
-    $ curl -OJL https://github.com/ykemiche/deepdespeckling/tarball/master
-
-Once you have a copy of the source, you can install it with:
-
-.. code-block:: console
-
-    $ python setup.py install
-
-
-.. _Github repo: https://github.com/ykemiche/deepdespeckling
-.. _tarball: https://github.com/ykemiche/deepdespeckling/tarball/master
diff --git a/docs/make.bat b/docs/make.bat
deleted file mode 100644
index ec3bf90..0000000
--- a/docs/make.bat
+++ /dev/null
@@ -1,36 +0,0 @@
-@ECHO OFF
-
-pushd %~dp0
-
-REM Command file for Sphinx documentation
-
-if "%SPHINXBUILD%" == "" (
-	set SPHINXBUILD=python -msphinx
-)
-set SOURCEDIR=.
-set BUILDDIR=_build
-set SPHINXPROJ=deepdespeckling
-
-if "%1" == "" goto help
-
-%SPHINXBUILD% >NUL 2>NUL
-if errorlevel 9009 (
-	echo.
-	echo.The Sphinx module was not found. Make sure you have Sphinx installed,
-	echo.then set the SPHINXBUILD environment variable to point to the full
-	echo.path of the 'sphinx-build' executable. Alternatively you may add the
-	echo.Sphinx directory to PATH.
-	echo.
-	echo.If you don't have Sphinx installed, grab it from
-	echo.http://sphinx-doc.org/
-	exit /b 1
-)
-
-%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
-goto end
-
-:help
-%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
-
-:end
-popd
diff --git a/docs/readme.rst b/docs/readme.rst
deleted file mode 100644
index 72a3355..0000000
--- a/docs/readme.rst
+++ /dev/null
@@ -1,1 +0,0 @@
-.. include:: ../README.rst
diff --git a/docs/usage.rst b/docs/usage.rst
deleted file mode 100644
index 00ed3b1..0000000
--- a/docs/usage.rst
+++ /dev/null
@@ -1,7 +0,0 @@
-=====
-Usage
-=====
-
-To use deepdespeckling in a project::
-
-    import deepdespeckling
diff --git a/requirements.txt b/requirements.txt
deleted file mode 100644
index 2db31a0..0000000
--- a/requirements.txt
+++ /dev/null
@@ -1,11 +0,0 @@
-wheel
-cycler            
-numpy           
-opencv-python     
-Pillow         
-pip               
-scipy            
-setuptools        
-torch         
-tqdm             
-
diff --git a/setup.cfg b/setup.cfg
deleted file mode 100644
index edba556..0000000
--- a/setup.cfg
+++ /dev/null
@@ -1,18 +0,0 @@
-[bumpversion]
-current_version = 0.1.0
-commit = True
-tag = True
-
-[bumpversion:file:setup.py]
-search = version='{current_version}'
-replace = version='{new_version}'
-
-[bumpversion:file:deepdespeckling/__init__.py]
-search = __version__ = '{current_version}'
-replace = __version__ = '{new_version}'
-
-[bdist_wheel]
-universal = 1
-
-[flake8]
-exclude = docs
diff --git a/setup.py b/setup.py
index de02cee..c9ed90e 100644
--- a/setup.py
+++ b/setup.py
@@ -4,15 +4,12 @@
 
 from setuptools import setup, find_packages
 
-with open('HISTORY.rst') as history_file:
-    history = history_file.read()
-
 requirements = []
-test_requirements = [ ]
+test_requirements = []
 
 setup(
-    author="Emanuele Dalsasso, Youcef Kemiche, Pierre Blanchard",
-    author_email='y.kemiche06@hotmail.com',
+    author="Emanuele Dalsasso, Youcef Kemiche, Pierre Blanchard, Hadrien Mariaccia",
+    author_email='engineer@hi-paris.fr',
     python_requires='>=3.6',
     classifiers=[
         'Development Status :: 2 - Pre-Alpha',
@@ -29,6 +26,7 @@
         'Programming Language :: Python :: 3.6',
         'Programming Language :: Python :: 3.7',
         'Programming Language :: Python :: 3.8',
+        'Programming Language :: Python :: 3.9',
     ],
     description="Python Boilerplate contains all the boilerplate you need to create a Python package.",
     entry_points={
@@ -36,7 +34,8 @@
             'deepdespeckling=deepdespeckling.cli:main',
         ],
     },
-    install_requires=["numpy","Pillow","scipy","torch","opencv-python","tqdm"],
+    install_requires=["numpy", "Pillow", "scipy",
+                      "torch", "opencv-python", "tqdm", "GDAL==3.8.1"],
     license="MIT license",
     include_package_data=True,
     keywords='deepdespeckling',
@@ -44,7 +43,7 @@
     packages=find_packages(include=['deepdespeckling', 'deepdespeckling.*']),
     test_suite='tests',
     tests_require=test_requirements,
-    url='https://github.com/ykemiche/deepdespeckling',
-    version='0.1.0',
+    url='https://github.com/hi-paris/deepdespeckling',
+    version='0.2.0',
     zip_safe=False,
 )
diff --git a/tox.ini b/tox.ini
index 91d6dbd..a6e0426 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,8 +1,9 @@
 [tox]
-envlist = py36, py37, py38, flake8
+envlist = py36, py37, py38, py39, flake8
 
 [travis]
 python =
+    3.9: py39
     3.8: py38
     3.7: py37
     3.6: py36


Test patch:
diff --git a/deepdespeckling/merlin/test/__init__.py b/deepdespeckling/merlin/test/__init__.py
deleted file mode 100644
index 965e8dd..0000000
--- a/deepdespeckling/merlin/test/__init__.py
+++ /dev/null
@@ -1,11 +0,0 @@
-"""Top-level package for merlin."""
-
-__author__ = """youcef kemiche"""
-__email__ = 'y.kemiche06@hotmail.com'
-__version__ = '0.1.0'
-
-from deepdespeckling.merlin.test.utils import *
-from deepdespeckling.merlin.test.load_cosar import *
-from deepdespeckling.merlin.test.model import *
-from deepdespeckling.merlin.test.model_test import *
-from deepdespeckling.merlin.test.spotlight import*
\ No newline at end of file
diff --git a/deepdespeckling/merlin/test/model_test.py b/deepdespeckling/merlin/test/model_test.py
deleted file mode 100644
index 206738b..0000000
--- a/deepdespeckling/merlin/test/model_test.py
+++ /dev/null
@@ -1,166 +0,0 @@
-from deepdespeckling.merlin.test.utils import *
-from deepdespeckling.merlin.test.model import *
-import torch
-import numpy as np
-from tqdm import tqdm
-import pathlib
-from pathlib import Path
-
-M = 10.089038980848645
-m = -1.429329123112601
-
-class Denoiser(object):
-    """ Description
-                ----------
-                A set of initial conditions, and transformations on the Y
-
-                Parameters
-                ----------
-                denoiser : an object
-
-                Returns
-                ----------
-    """
-
-
-    def __init__(self, input_c_dim=1):
-
-        self.input_c_dim = input_c_dim
-
-    def load(self,model,weights_path):
-        """ Description
-                    ----------
-                    Restores a checkpoint located in a checkpoint repository
-
-                    Parameters
-                    ----------
-                    checkpoint_dir : a path leading to the checkpoint file
-
-                    Returns
-                    ----------
-                    True : Restoration is a success
-                    False: Restoration has failed
-        """
-        print("[*] Loading the model...")
-
-        model.load_state_dict(torch.load(weights_path))
-
-        return model
-
-
-    def test(self,test_files, weights_path, save_dir,stride,patch_size):
-
-
-        """ Description
-                    ----------
-                    The function that does the job. Should be merged with main.py ?
-
-                    Parameters
-                    ----------
-                    test_files : a path leading to the checkpoint file
-                    ckpt_dir : repository containing the checkpoint (and weights)
-                    save_dir : repository to save sar images, real images and noisy images
-                    dataset_dir : the path to the test data
-                    stride : number of bytes from one row of pixels in memory to the next row of pixels in memory
-
-                    Returns
-                    ----------
-                    True : Restoration is a success
-                    False: Restoration has failed
-
-        """
-
-        """Test MERLIN"""
-
-        assert len(test_files) != 0, 'No testing data!'
-
-        loaded_model = Model(torch.device("cuda:0" if torch.cuda.is_available() else "cpu"))
-        loaded_model.load_state_dict(torch.load(weights_path, map_location=torch.device('cpu')))
-
-        # loaded_model = self.load(model,weights_path)
-        print()
-        print(" [*] Load weights SUCCESS...")
-        print("[*] start testing...")
-
-        for idx in range(len(test_files)):
-            real_image = load_sar_images(test_files[idx]).astype(np.float32)
-            i_real_part = (real_image[:, :, :, 0]).reshape(real_image.shape[0], real_image.shape[1],
-                                                           real_image.shape[2], 1)
-            i_imag_part = (real_image[:, :, :, 1]).reshape(real_image.shape[0], real_image.shape[1],
-                                                           real_image.shape[2], 1)
-
-            # Pad the image
-            im_h = np.size(real_image, 1)
-            im_w = np.size(real_image, 2)
-
-            count_image = np.zeros(i_real_part.shape)
-            output_clean_image_1 = np.zeros(i_real_part.shape)
-            output_clean_image_2 = np.zeros(i_real_part.shape)
-
-            if im_h == patch_size:
-                x_range = list(np.array([0]))
-
-            else:
-                x_range = list(range(0, im_h - patch_size, stride))
-                if (x_range[-1] + patch_size) < im_h: x_range.extend(range(im_h - patch_size, im_h - patch_size + 1))
-
-            if im_w == patch_size:
-                y_range = list(np.array([0]))
-
-            else:
-                y_range = list(range(0, im_w - patch_size, stride))
-                if (y_range[-1] + patch_size) < im_w: y_range.extend(range(im_w - patch_size, im_w - patch_size + 1))
-
-            for x in tqdm(x_range):
-                for y in y_range:
-
-                    real_to_denoise, imag_to_denoise = symetrisation_patch_test(i_real_part[:, x:x + patch_size, y:y + patch_size, :],i_imag_part[:, x:x + patch_size, y:y + patch_size, :])
-
-                    real_to_denoise =torch.tensor(real_to_denoise)
-                    imag_to_denoise=torch.tensor(imag_to_denoise)
-
-                    real_to_denoise = real_to_denoise.type(torch.float32)
-                    imag_to_denoise = imag_to_denoise.type(torch.float32)
-
-                    real_to_denoise=(torch.log(torch.square(real_to_denoise)+1e-3)-2*m)/(2*(M-m))
-                    imag_to_denoise=(torch.log(torch.square(imag_to_denoise)+1e-3)-2*m)/(2*(M-m))
-
-
-                    tmp_clean_image_real = loaded_model.forward(real_to_denoise).detach().numpy()
-                    tmp_clean_image_real=np.moveaxis(tmp_clean_image_real, 1, -1)
-
-                    output_clean_image_1[:, x:x + patch_size, y:y + patch_size, :] = output_clean_image_1[:, x:x + patch_size,
-                                                                                 y:y + patch_size,
-                                                                                 :] + tmp_clean_image_real
-
-
-
-                    tmp_clean_image_imag = loaded_model.forward(imag_to_denoise).detach().numpy()
-                    tmp_clean_image_imag=np.moveaxis(tmp_clean_image_imag, 1, -1)
-
-                    output_clean_image_2[:, x:x + patch_size, y:y + patch_size, :] = output_clean_image_2[:, x:x + patch_size,
-                                                                                 y:y + patch_size,
-                                                                                 :] + tmp_clean_image_imag
-                    count_image[:, x:x + patch_size, y:y + patch_size, :] = count_image[:, x:x + patch_size, y:y + patch_size,
-                                                                        :] + np.ones((1, patch_size, patch_size, 1))
-
-
-            output_clean_image_1 = output_clean_image_1 / count_image
-            output_clean_image_2 = output_clean_image_2 / count_image
-            output_clean_image = 0.5 * (np.square(denormalize_sar(output_clean_image_1)) + np.square(
-            denormalize_sar(output_clean_image_2))) # combine the two estimation
-
-
-            noisyimage = np.squeeze(np.sqrt(i_real_part ** 2 + i_imag_part ** 2))
-            outputimage = np.sqrt(np.squeeze(output_clean_image))
-
-            p = Path(test_files[idx])
-            imagename = p.name
-
-            print("Denoised image %s" % imagename)
-
-            save_sar_images(outputimage, noisyimage, imagename, save_dir)
-            save_real_imag_images(noisyimage, denormalize_sar(output_clean_image_1), denormalize_sar(output_clean_image_2),
-                                  imagename, save_dir)
-
-            save_real_imag_images_noisy(noisyimage, np.squeeze(i_real_part), np.squeeze(i_imag_part), imagename, save_dir)
diff --git a/deepdespeckling/merlin/test/results/denoised_imag_test_image_data.png b/deepdespeckling/merlin/test/results/denoised_imag_test_image_data.png
deleted file mode 100644
index 8aa904a..0000000
Binary files a/deepdespeckling/merlin/test/results/denoised_imag_test_image_data.png and /dev/null differ
diff --git a/deepdespeckling/merlin/test/spotlight.py b/deepdespeckling/merlin/test/spotlight.py
deleted file mode 100644
index 09e8925..0000000
--- a/deepdespeckling/merlin/test/spotlight.py
+++ /dev/null
@@ -1,190 +0,0 @@
-import torch
-
-from deepdespeckling.merlin.test.model import *
-from deepdespeckling.merlin.test.utils import *
-from deepdespeckling.merlin.test.model_test import *
-import os
-from glob import glob
-import pathlib
-from pathlib import Path
-
-import numpy as np
-from deepdespeckling.merlin.test.load_cosar import cos2mat
-
-M = 10.089038980848645
-m = -1.429329123112601
-
-this_dir, this_filename = os.path.split(__file__)
-
-
-def despeckle_spotlight(image_path, destination_directory, stride_size=64,
-                        model_weights_path=os.path.join(this_dir, "saved_model", "spotlight.pth"), patch_size=256):
-    """ Description
-            ----------
-            Runs a test instance by calling the test function defined in model.py on a few samples
-
-            Parameters
-            ----------
-            denoiser : an object
-
-            Returns
-            ----------
-
-    """
-
-    denoiser = Denoiser()
-
-    if not os.path.exists(destination_directory + '/processed_image'):
-        os.mkdir(destination_directory + '/processed_image')
-
-    test_data = destination_directory + '/processed_image'
-
-    filelist = glob(os.path.join(test_data, "*"))
-    for f in filelist:
-        os.remove(f)
-
-    # check the extension of the file
-    if Path(image_path).suffix == ".npy":
-        image_data = np.load(image_path)
-    else:
-        image_data = cos2mat(image_path)
-
-    imagename = image_path.split('/')[-1].split('.')[0]
-    np.save(test_data + '/' + imagename +'.npy', image_data)
-
-    print(
-        "[*] Start testing on real data. Working directory: %s. Collecting data from %s and storing test results in %s" % (
-            os.getcwd(), image_path, destination_directory))
-
-    test_files = glob((test_data + '/*.npy'))
-
-    denoiser.test(test_files, model_weights_path, save_dir=destination_directory,
-                  stride=stride_size, patch_size=patch_size)
-
-
-def despeckle_from_coordinates_spotlight(image_path, coordinates_dict, destination_directory, stride_size=64,
-                                         model_weights_path=os.path.join(this_dir, "saved_model", "spotlight.pth"),
-                                         patch_size=256):
-    """ Description
-            ----------
-            Runs a test instance by calling the test function defined in model.py on a few samples
-
-            Parameters
-            ----------
-            denoiser : an object
-
-            Returns
-            ----------
-
-    """
-
-    x_start = coordinates_dict["x_start"]
-    x_end = coordinates_dict["x_end"]
-    y_start = coordinates_dict["y_start"]
-    y_end = coordinates_dict["y_end"]
-
-    denoiser = Denoiser()
-
-    if not os.path.exists(destination_directory + '/processed_image'):
-        os.mkdir(destination_directory + '/processed_image')
-
-    test_data = destination_directory + '/processed_image'
-
-    filelist = glob(os.path.join(test_data, "*"))
-    for f in filelist:
-        os.remove(f)
-
-    # check the extension of the file
-    if Path(image_path).suffix == ".npy":
-        image_data = np.load(image_path)
-    else:
-        image_data = cos2mat(image_path)
-
-    imagename = image_path.split('/')[-1].split('.')[0]
-    np.save(test_data + '/' + imagename +'.npy', image_data[x_start:x_end, y_start:y_end, :])
-
-    print(
-        "[*] Start testing on real data. Working directory: %s. Collecting data from %s and storing test results in %s" % (
-            os.getcwd(), image_path, destination_directory))
-
-    test_files = glob((test_data + '/*.npy'))
-
-    denoiser.test(test_files, model_weights_path, save_dir=destination_directory,
-                  stride=stride_size, patch_size=patch_size)
-
-
-def despeckle_from_crop_spotlight(image_path, destination_directory, stride_size=64,
-                                  model_weights_path=os.path.join(this_dir, "saved_model", "spotlight.pth"),
-                                  patch_size=256,
-                                  fixed=True):
-    print('value ofd fixed in despeckle from crop', fixed)
-    """ The despeckling function with an integrated cropping tool made with OpenCV.
-    The ideal choice if you need to despeckle only a certain area of your high-res image. Results are saved in the
-    directory provided in the 'destination directory'
-            Parameters
-            ----------
-            image_path: string
-            the path leading to the image to be despceckled
-            destination_directory: string
-            path leading to the results folder
-            stride_size: integer or tuple
-            stride of the autoencoder
-            model_weights_path: string
-            path leading to the weights of our pre-trained model. Value by default is our weights.
-            patch_size: integer
-            Area size of the sub-image to be processed. Value by default is 256.
-            height: integer
-            Height of the image. Value by default is 256.
-            width: integer
-            Width of the image. Value by default is 256.
-            fixed: bool
-            If True, crop size is limited to 256*256
-            Returns
-            ----------
-            None
-    """
-
-    denoiser = Denoiser()
-
-    if not os.path.exists(destination_directory + '\\processed_image'):
-        os.mkdir(destination_directory + '\\processed_image')
-
-    test_data = destination_directory + '\\processed_image'
-
-    filelist = glob(os.path.join(test_data, "*"))
-    for f in filelist:
-        os.remove(f)
-
-    # FROM IMAGE PATH RETRIEVE PNG, NPY, REAL , IMAG, THRESHOLD, FILENAME
-    image_png, image_data, image_data_real, image_data_imag, threshold, filename = get_info_image(image_path,
-                                                                                                  destination_directory)
-
-    # CROPPING OUR PNG AND REFLECT THE CROP ON REAL AND IMAG
-    if fixed:
-        print('Fixed mode selected')
-        crop_fixed(image_png, image_data_real, image_data_imag, destination_directory, test_data)
-    else:
-        print('Free mode selected')
-        crop(image_png, image_data_real, image_data_imag, destination_directory, test_data)
-
-    image_data_real_cropped = np.load(test_data + '\\image_data_real_cropped.npy')
-    store_data_and_plot(image_data_real_cropped, threshold, test_data + '\\image_data_real_cropped.npy')
-    image_data_imag_cropped = np.load(test_data + '\\image_data_imag_cropped.npy')
-    store_data_and_plot(image_data_imag_cropped, threshold, test_data + '\\image_data_imag_cropped.npy')
-
-    image_data_real_cropped = image_data_real_cropped.reshape(image_data_real_cropped.shape[0],
-                                                              image_data_real_cropped.shape[1], 1)
-    image_data_imag_cropped = image_data_imag_cropped.reshape(image_data_imag_cropped.shape[0],
-                                                              image_data_imag_cropped.shape[1], 1)
-
-    p = Path(image_path)
-    np.save(test_data + '/' + p.stem + '_cropped',
-            np.concatenate((image_data_real_cropped, image_data_imag_cropped), axis=2))
-
-    print(
-        "[*] Start testing on real data. Working directory: %s. Collecting data from %s and storing test results in %s" % (
-            os.getcwd(), image_path, destination_directory)+"\n")
-
-    test_files = glob((test_data + '/' + p.stem + '_cropped.npy'))
-    denoiser.test(test_files, model_weights_path, save_dir=destination_directory,
-                  stride=stride_size, patch_size=patch_size)
diff --git a/deepdespeckling/merlin/test/stripmap.py b/deepdespeckling/merlin/test/stripmap.py
deleted file mode 100644
index d8320f8..0000000
--- a/deepdespeckling/merlin/test/stripmap.py
+++ /dev/null
@@ -1,192 +0,0 @@
-import torch
-
-from deepdespeckling.merlin.test.model import *
-from deepdespeckling.merlin.test.utils import *
-from deepdespeckling.merlin.test.model_test import *
-import os
-from glob import glob
-import pathlib
-from pathlib import Path
-
-import numpy as np
-from deepdespeckling.merlin.test.load_cosar import cos2mat
-
-M = 10.089038980848645
-m = -1.429329123112601
-
-this_dir, this_filename = os.path.split(__file__)
-
-
-def despeckle_stripmap(image_path, destination_directory, stride_size=64,
-              model_weights_path=os.path.join(this_dir, "saved_model", "stripmap.pth"), patch_size=256):
-
-    """ Description
-            ----------
-            Runs a test instance by calling the test function defined in model.py on a few samples
-
-            Parameters
-            ----------
-            denoiser : an object
-
-            Returns
-            ----------
-
-    """
-
-    denoiser = Denoiser()
-
-    if not os.path.exists(destination_directory + '/processed_image'):
-        os.mkdir(destination_directory + '/processed_image')
-
-    test_data = destination_directory + '/processed_image'
-
-    filelist = glob(os.path.join(test_data, "*"))
-    for f in filelist:
-        os.remove(f)
-
-     # check the extension of the file
-    if Path(image_path).suffix==".npy":
-        image_data=np.load(image_path)
-    else:
-        image_data = cos2mat(image_path)
-
-    imagename = image_path.split('/')[-1].split('.')[0]
-    np.save(test_data + '/' + imagename +'.npy', image_data)
-
-    print(
-        "[*] Start testing on real data. Working directory: %s. Collecting data from %s and storing test results in %s" % (
-            os.getcwd(), destination_directory, destination_directory))
-
-    test_files = glob((test_data + '/*.npy'))
-    print(test_files)
-
-    denoiser.test(test_files, model_weights_path, save_dir=destination_directory,
-                  stride=stride_size, patch_size=patch_size)
-
-
-def despeckle_from_coordinates_stripmap(image_path, coordinates_dict, destination_directory, stride_size=64,
-                               model_weights_path=os.path.join(this_dir, "saved_model", "stripmap.pth"), patch_size=256):
-    """ Description
-            ----------
-            Runs a test instance by calling the test function defined in model.py on a few samples
-
-            Parameters
-            ----------
-            denoiser : an object
-
-            Returns
-            ----------
-
-    """
-
-    x_start = coordinates_dict["x_start"]
-    x_end = coordinates_dict["x_end"]
-    y_start = coordinates_dict["y_start"]
-    y_end = coordinates_dict["y_end"]
-
-    denoiser = Denoiser()
-
-    if not os.path.exists(destination_directory + '/processed_image'):
-        os.mkdir(destination_directory + '/processed_image')
-
-    test_data = destination_directory + '/processed_image'
-
-    filelist = glob(os.path.join(test_data, "*"))
-    for f in filelist:
-        os.remove(f)
-
-
-   # check the extension of the file
-    if Path(image_path).suffix==".npy":
-        image_data=np.load(image_path)
-    else:
-        image_data = cos2mat(image_path)
-
-    imagename = image_path.split('/')[-1].split('.')[0]
-    np.save(test_data + '/' + imagename +'.npy', image_data[x_start:x_end, y_start:y_end, :])
-
-
-    print(
-        "[*] Start testing on real data. Working directory: %s. Collecting data from %s and storing test results in %s" % (
-            os.getcwd(), destination_directory, destination_directory))
-
-    test_files = glob((test_data + '/*.npy'))
-    print(test_files)
-    denoiser.test(test_files, model_weights_path, save_dir=destination_directory,
-                  stride=stride_size, patch_size=patch_size)
-
-
-def despeckle_from_crop_stripmap(image_path, destination_directory, stride_size=64,
-                        model_weights_path=os.path.join(this_dir, "saved_model", "stripmap.pth"), patch_size=256,
-                        fixed=True):
-    print('value ofd fixed in despeckle from crop', fixed)
-    """ The despeckling function with an integrated cropping tool made with OpenCV.
-    The ideal choice if you need to despeckle only a certain area of your high-res image. Results are saved in the
-    directory provided in the 'destination directory'
-            Parameters
-            ----------
-            image_path: string
-            the path leading to the image to be despceckled
-            destination_directory: string
-            path leading to the results folder
-            stride_size: integer or tuple
-            stride of the autoencoder
-            model_weights_path: string
-            path leading to the weights of our pre-trained model. Value by default is our weights.
-            patch_size: integer
-            Area size of the sub-image to be processed. Value by default is 256.
-            height: integer
-            Height of the image. Value by default is 256.
-            width: integer
-            Width of the image. Value by default is 256.
-            fixed: bool
-            If True, crop size is limited to 256*256
-            Returns
-            ----------
-            None
-    """
-
-    denoiser = Denoiser()
-
-    if not os.path.exists(destination_directory + '\\processed_image'):
-        os.mkdir(destination_directory + '\\processed_image')
-
-    test_data = destination_directory + '\\processed_image'
-
-    filelist = glob(os.path.join(test_data, "*"))
-    for f in filelist:
-        os.remove(f)
-
-    # FROM IMAGE PATH RETRIEVE PNG, NPY, REAL , IMAG, THRESHOLD, FILENAME
-    image_png, image_data, image_data_real, image_data_imag, threshold, filename = get_info_image(image_path,
-                                                                                                  destination_directory)
-
-    # CROPPING OUR PNG AND REFLECT THE CROP ON REAL AND IMAG
-    if fixed:
-        print('Fixed mode selected')
-        crop_fixed(image_png, image_data_real, image_data_imag, destination_directory, test_data)
-    else:
-        print('Free mode selected')
-        crop(image_png, image_data_real, image_data_imag, destination_directory, test_data)
-
-    image_data_real_cropped = np.load(test_data + '\\image_data_real_cropped.npy')
-    store_data_and_plot(image_data_real_cropped, threshold, test_data + '\\image_data_real_cropped.npy')
-    image_data_imag_cropped = np.load(test_data + '\\image_data_imag_cropped.npy')
-    store_data_and_plot(image_data_imag_cropped, threshold, test_data + '\\image_data_imag_cropped.npy')
-
-    image_data_real_cropped = image_data_real_cropped.reshape(image_data_real_cropped.shape[0],
-                                                              image_data_real_cropped.shape[1], 1)
-    image_data_imag_cropped = image_data_imag_cropped.reshape(image_data_imag_cropped.shape[0],
-                                                              image_data_imag_cropped.shape[1], 1)
-
-    p = Path(image_path)
-    np.save(test_data + '/' + p.stem + '_cropped',
-            np.concatenate((image_data_real_cropped, image_data_imag_cropped), axis=2))
-
-    print(
-        "[*] Start testing on real data. Working directory: %s. Collecting data from %s and storing test results in %s" % (
-            os.getcwd(), image_path, destination_directory)+"\n")
-
-    test_files = glob((test_data + '/' + p.stem + '_cropped.npy'))
-    denoiser.test(test_files, model_weights_path, save_dir=destination_directory,
-                  stride=stride_size, patch_size=patch_size)
diff --git a/deepdespeckling/merlin/test/utils.py b/deepdespeckling/merlin/test/utils.py
deleted file mode 100644
index ef75d56..0000000
--- a/deepdespeckling/merlin/test/utils.py
+++ /dev/null
@@ -1,553 +0,0 @@
-import numpy as np
-from PIL import Image
-from scipy import special
-from scipy import signal
-from deepdespeckling.merlin.test.load_cosar import cos2mat
-import cv2
-from PIL import Image
-import numpy as np
-from numpy import asarray
-import os
-
-
-# DEFINE PARAMETERS OF SPECKLE AND NORMALIZATION FACTOR
-M = 10.089038980848645
-m = -1.429329123112601
-L = 1
-c = (1 / 2) * (special.psi(L) - np.log(L))
-cn = c / (M - m)  # normalized (0,1) mean of log speckle
-
-
-def normalize_sar(im):
-    """ Description
-            ----------
-            Normalization of a numpy-stored image
-
-            Parameters
-            ----------
-            im : an image object
-
-            Returns
-            ----------
-
-        """
-    if not isinstance(im, np.ndarray):
-        raise TypeError('Please provide a .npy argument')
-    return ((np.log(im + np.spacing(1)) - m) * 255 / (M - m)).astype('float32')
-
-
-def denormalize_sar(im):
-    """ Description
-            ----------
-            Denormalization of a numpy image
-
-            Parameters
-            ----------
-            im : an image object
-
-            Returns
-            ----------
-
-        """
-    if not isinstance(im, np.ndarray):
-        raise TypeError('Please provide a .npy argument')
-    return np.exp((M - m) * (np.squeeze(im)).astype('float32') + m)
-
-
-def symetrisation_patch_test(real_part, imag_part):
-    if not isinstance(real_part, np.ndarray):
-        raise TypeError('Please provide a .npy argument')
-    if not isinstance(imag_part, np.ndarray):
-        raise TypeError('Please provide a .npy argument')
-    S = np.fft.fftshift(np.fft.fft2(real_part[0, :, :, 0] + 1j * imag_part[0, :, :, 0]))
-    p = np.zeros((S.shape[0]))  # azimut (ncol)
-    for i in range(S.shape[0]):
-        p[i] = np.mean(np.abs(S[i, :]))
-    sp = p[::-1]
-    c = np.real(np.fft.ifft(np.fft.fft(p) * np.conjugate(np.fft.fft(sp))))
-    d1 = np.unravel_index(c.argmax(), p.shape[0])
-    d1 = d1[0]
-    shift_az_1 = int(round(-(d1 - 1) / 2)) % p.shape[0] + int(p.shape[0] / 2)
-    p2_1 = np.roll(p, shift_az_1)
-    shift_az_2 = int(round(-(d1 - 1 - p.shape[0]) / 2)) % p.shape[0] + int(p.shape[0] / 2)
-    p2_2 = np.roll(p, shift_az_2)
-    window = signal.gaussian(p.shape[0], std=0.2 * p.shape[0])
-    test_1 = np.sum(window * p2_1)
-    test_2 = np.sum(window * p2_2)
-    # make sure the spectrum is symetrized and zeo-Doppler centered
-    if test_1 >= test_2:
-        p2 = p2_1
-        shift_az = shift_az_1 / p.shape[0]
-    else:
-        p2 = p2_2
-        shift_az = shift_az_2 / p.shape[0]
-    S2 = np.roll(S, int(shift_az * p.shape[0]), axis=0)
-
-    q = np.zeros((S.shape[1]))  # range (nlin)
-    for j in range(S.shape[1]):
-        q[j] = np.mean(np.abs(S[:, j]))
-    sq = q[::-1]
-    # correlation
-    cq = np.real(np.fft.ifft(np.fft.fft(q) * np.conjugate(np.fft.fft(sq))))
-    d2 = np.unravel_index(cq.argmax(), q.shape[0])
-    d2 = d2[0]
-    shift_range_1 = int(round(-(d2 - 1) / 2)) % q.shape[0] + int(q.shape[0] / 2)
-    q2_1 = np.roll(q, shift_range_1)
-    shift_range_2 = int(round(-(d2 - 1 - q.shape[0]) / 2)) % q.shape[0] + int(q.shape[0] / 2)
-    q2_2 = np.roll(q, shift_range_2)
-    window_r = signal.gaussian(q.shape[0], std=0.2 * q.shape[0])
-    test_1 = np.sum(window_r * q2_1)
-    test_2 = np.sum(window_r * q2_2)
-    if test_1 >= test_2:
-        q2 = q2_1
-        shift_range = shift_range_1 / q.shape[0]
-    else:
-        q2 = q2_2
-        shift_range = shift_range_2 / q.shape[0]
-
-    Sf = np.roll(S2, int(shift_range * q.shape[0]), axis=1)
-    ima2 = np.fft.ifft2(np.fft.ifftshift(Sf))
-    ima2 = ima2.reshape(1, np.size(ima2, 0), np.size(ima2, 1), 1)
-    return np.real(ima2), np.imag(ima2)
-
-
-def load_sar_images(filelist):
-    """ Description
-            ----------
-            Loads files , resize them and append them into a list called data
-
-            Parameters
-            ----------
-            filelist : a path to a folder containing the images
-
-            Returns
-            ----------
-            A list of images
-
-        """
-    if not isinstance(filelist, list):
-        im = np.load(filelist)
-        return np.array(im).reshape(1, np.size(im, 0), np.size(im, 1), 2)
-    data = []
-    for file in filelist:
-        im = np.load(file)
-        data.append(np.array(im).reshape(1, np.size(im, 0), np.size(im, 1), 2))
-    return data
-
-
-def store_data_and_plot(im, threshold, filename):
-    """ Description
-            ----------
-            Creates an image memory from an object exporting the array interface and returns a
-            converted copy of this image into greyscale mode ("L")
-
-            However, there is not plotting functions' call ?
-
-            Parameters
-            ----------
-            im : the image to store
-            threshold: clip a maximum value in the image array i.e values are to be between 0 and threshold
-            filename: the path to store the result array image in .png
-
-            Returns
-            ----------
-            None
-
-        """
-    if not isinstance(im, np.ndarray):
-        raise TypeError('Please provide a .npy argument')
-    im = np.clip(im, 0, threshold)
-
-    im = im / threshold * 255
-    im = Image.fromarray(im.astype('float64')).convert('L')
-    im.save(filename.replace('npy', 'png'))
-    return filename
-
-
-def save_sar_images(denoised, noisy, imagename, save_dir, groundtruth=None):
-    choices = {'marais1': 190.92, 'marais2': 168.49, 'saclay': 470.92, 'lely': 235.90, 'ramb': 167.22,
-               'risoul': 306.94, 'limagne': 178.43, 'saintgervais': 560, 'Serreponcon': 450.0,
-               'Sendai': 600.0, 'Paris': 1291.0, 'Berlin': 1036.0, 'Bergen': 553.71,
-               'SDP_Lambesc': 349.53, 'Grand_Canyon': 287.0, 'domancy': 560, 'Brazil': 103.0}
-    threshold = None
-    for x in choices:
-        if x in imagename:
-            threshold = choices.get(x)
-    if threshold is None: threshold = np.mean(noisy) + 3 * np.std(noisy)
-
-    ####
-    imagename = imagename.split('\\')[-1]
-    ####
-
-    if groundtruth:
-        groundtruthfilename = save_dir + "/groundtruth_" + imagename
-        np.save(groundtruthfilename, groundtruth)
-        store_data_and_plot(groundtruth, threshold, groundtruthfilename)
-
-    denoisedfilename = save_dir + "/denoised_" + imagename
-    np.save(denoisedfilename, denoised)
-    store_data_and_plot(denoised, threshold, denoisedfilename)
-
-    noisyfilename = save_dir + "/noisy_" + imagename
-    np.save(noisyfilename, noisy)
-    store_data_and_plot(noisy, threshold, noisyfilename)
-
-
-def save_real_imag_images(noisy, real_part, imag_part, imagename, save_dir):
-    choices = {'marais1': 190.92, 'marais2': 168.49, 'saclay': 470.92, 'lely': 235.90, 'ramb': 167.22,
-               'risoul': 306.94, 'limagne': 178.43, 'saintgervais': 560, 'Serreponcon': 450.0,
-               'Sendai': 600.0, 'Paris': 1291.0, 'Berlin': 1036.0, 'Bergen': 553.71,
-               'SDP_Lambesc': 349.53, 'Grand_Canyon': 287.0, 'Brazil': 103.0}
-    threshold = None
-    for x in choices:
-        if x in imagename:
-            threshold = choices.get(x)
-    if threshold is None: threshold = np.mean(noisy) + 3 * np.std(noisy)
-
-    ####
-    imagename = imagename.split('\\')[-1]
-    ####
-
-    realfilename = save_dir + "/denoised_real_" + imagename
-    np.save(realfilename, real_part)
-    store_data_and_plot(real_part, threshold, realfilename)
-
-    imagfilename = save_dir + "/denoised_imag_" + imagename
-    np.save(imagfilename, imag_part)
-    store_data_and_plot(imag_part, threshold, imagfilename)
-
-
-def save_real_imag_images_noisy(noisy, real_part, imag_part, imagename, save_dir):
-    choices = {'marais1': 190.92, 'marais2': 168.49, 'saclay': 470.92, 'lely': 235.90, 'ramb': 167.22,
-               'risoul': 306.94, 'limagne': 178.43, 'saintgervais': 560, 'Serreponcon': 450.0,
-               'Sendai': 600.0, 'Paris': 1291.0, 'Berlin': 1036.0, 'Bergen': 553.71,
-               'SDP_Lambesc': 349.53, 'Grand_Canyon': 287.0, 'Brazil': 103.0}
-    threshold = None
-    for x in choices:
-        if x in imagename:
-            threshold = choices.get(x)
-    if threshold is None: threshold = np.mean(np.abs(noisy)) + 3 * np.std(np.abs(noisy))
-
-    ####
-    imagename = imagename.split('\\')[-1]
-    ####
-
-    realfilename = save_dir + "/noisy_real_" + imagename
-    np.save(realfilename, real_part)
-    store_data_and_plot(np.sqrt(2) * np.abs(real_part), threshold, realfilename)
-
-    imagfilename = save_dir + "/noisy_imag_" + imagename
-    np.save(imagfilename, imag_part)
-    store_data_and_plot(np.sqrt(2) * np.abs(imag_part), threshold, imagfilename)
-
-
-def cal_psnr(Shat, S):
-    # takes amplitudes in input
-    # Shat: a SAR amplitude image
-    # S:    a reference SAR image
-    P = np.quantile(S, 0.99)
-    res = 10 * np.log10((P ** 2) / np.mean(np.abs(Shat - S) ** 2))
-    return res
-
-
-def crop_fixed(image_png, image_data_real, image_data_imag, destination_directory, test_data):
-    """ A crapping tool for despeckling only the selection of the user, made with OpenCV
-
-            Parameters
-            ----------
-            image_png: .png file
-            the image to be cropped in png format
-
-            image_data_real: nd.array
-            the real part of the image stored in an array
-
-            image_data_imag: nd.array
-            the imaginary part of the image stored in an array
-
-            destination_directory: string
-            the path for saving results in
-
-            test_data: string
-            the path for saving results in
-
-            cropping: bool
-            A boolean stating if the user wants to crop the image or not
-
-
-            Returns
-            ----------
-            None
-
-        """
-
-    test_data = destination_directory + '\\processed_image'
-
-    # HERE I READ THE PNG FILE
-    oriImage = image_png.copy()
-    cropping = False
-    x_start, y_start, x_end, y_end = 0, 0, 0, 0
-
-    # CV2 CROPPING IN WINDOW
-    def mouse_crop_fixed(event, x, y, flags, param):
-        """ The callback function of crop() to deal with user's events
-        """
-        global x_start, y_start, x_end, y_end, cropping
-        cropping = False
-        if event == cv2.EVENT_LBUTTONDOWN:
-            x_start, y_start, x_end, y_end = x, y, x, y
-            cropping = True
-
-        # Mouse is Moving
-        elif event == cv2.EVENT_MOUSEMOVE:
-            x_end, y_end = x, y
-
-        # if the left mouse button was released
-        elif event == cv2.EVENT_LBUTTONUP:
-            # record the ending (x, y) coordinates
-            x_end, y_end = x, y
-            # case crop is done bottom right - top left : WORKS
-            if x_start > x_end and y_start > y_end:
-                tempxstart = x_start
-                tempystart = y_start
-
-                x_start = tempxstart - 32
-                x_end = tempxstart
-
-                y_start = tempystart - 32
-                y_end = tempystart
-
-            elif x_start > x_end and y_start < y_end:
-                tempxstart = x_start
-                tempystart = y_start
-
-                x_start = tempxstart - 32
-                y_start = tempystart
-
-                x_end = tempxstart
-                y_end = tempystart + 32
-
-            elif x_start < x_end and y_start > y_end:
-                tempxstart = x_start
-                tempystart = y_start
-
-                x_start = tempxstart
-                y_start = tempystart - 32
-                x_end = tempxstart + 32
-                y_end = tempystart
-
-            else:
-                x_end = x_start + 32
-                y_end = y_start + 32
-
-            refPoint = [(x_start, y_start), (x_end, y_end)]
-            ## cropping is finished
-            cv2.rectangle(image, (x_start, y_start), (x_end, y_end), (255, 0, 0), 2)
-            cropping = False
-
-            if len(refPoint) == 2:  # when two points were found
-                image_data_real_cropped = image_data_real[refPoint[0][1] * 8:refPoint[1][1] * 8,
-                                          refPoint[0][0] * 8:refPoint[1][0] * 8]
-                image_data_imag_cropped = image_data_imag[refPoint[0][1] * 8:refPoint[1][1] * 8,
-                                          refPoint[0][0] * 8:refPoint[1][0] * 8]
-                roi = oriImage[refPoint[0][1] * 8:refPoint[1][1] * 8, refPoint[0][0] * 8:refPoint[1][0] * 8]
-                roi = cv2.resize(roi, (256, 256))
-                cv2.imwrite(destination_directory + '\\cropped_npy_to_png.png', roi)
-                cv2.imshow("Cropped", roi)
-                cropped_img_png = Image.open(destination_directory + '\\cropped_npy_to_png.png')
-                numpy_crop = asarray(cropped_img_png)
-                np.save(destination_directory + '\\cropped.npy', numpy_crop)
-                np.save(test_data + '\\image_data_real_cropped.npy', image_data_real_cropped)
-                np.save(test_data + '\\image_data_imag_cropped.npy', image_data_imag_cropped)
-
-    h, w, c = image_png.shape
-    # resizing image
-    image = cv2.resize(image_png, (int(w / 8), int(h / 8)))
-    cv2.namedWindow("image")
-    cv2.setMouseCallback("image", mouse_crop_fixed)
-
-    while True:
-        i = image.copy()
-
-        if not cropping:
-            cv2.imshow("image", image)
-
-        elif cropping:
-            cv2.imshow("image", i)
-
-        key = cv2.waitKey(10)
-        if key == ord('q'):
-            cv2.destroyAllWindows()
-            return
-
-
-def crop(image_png, image_data_real, image_data_imag, destination_directory,test_data):
-    """ A crapping tool for despeckling only the selection of the user, made with OpenCV
-
-            Parameters
-            ----------
-            image_png: .png file
-            the image to be cropped in png format
-
-            image_data_real: nd.array
-            the real part of the image stored in an array
-
-            image_data_imag: nd.array
-            the imaginary part of the image stored in an array
-
-            destination_directory: string
-            the path for saving results in
-
-            test_data: string
-            the path for saving results in
-
-            cropping: bool
-            A boolean stating if the user wants to crop the image or not
-
-
-            Returns
-            ----------
-            None
-
-        """
-    test_data = destination_directory + '\\processed_image'
-
-    # HERE I READ THE PNG FILE
-    oriImage = image_png.copy()
-    cropping = False
-    x_start, y_start, x_end, y_end = 0, 0, 0, 0
-
-    # CV2 CROPPING IN WINDOW
-    def mouse_crop(event, x, y, flags, param):
-        """ The callback function of crop() to deal with user's events
-        """
-        global x_start, y_start, x_end, y_end, cropping
-        cropping = False
-
-        if event == cv2.EVENT_LBUTTONDOWN:
-            x_start, y_start, x_end, y_end = x, y, x, y
-            cropping = True
-
-        # Mouse is Moving
-        elif event == cv2.EVENT_MOUSEMOVE:
-            x_end, y_end = x, y
-
-        # if the left mouse button was released
-        elif event == cv2.EVENT_LBUTTONUP:
-            # record the ending (x, y) coordinates
-            x_end, y_end = x, y
-            # case crop is done bottom right - top left : WORKS
-            if x_start > x_end and y_start > y_end:
-                tempx = x_start
-                x_start = x_end
-                x_end = tempx
-
-                tempy = y_start
-                y_start = y_end
-                y_end = tempy
-
-            elif x_start > x_end and y_start < y_end:
-                tempxstart = x_start
-                tempystart = y_start
-                tempxend = x_end
-                tempyend = y_end
-
-                x_start = tempxend
-                y_start = tempystart
-                x_end = tempxstart
-                y_end = tempyend
-
-            elif x_start < x_end and y_start > y_end:
-                tempxstart = x_start
-                tempystart = y_start
-                tempxend = x_end
-                tempyend = y_end
-
-                x_start = tempxstart
-                y_start = tempyend
-                x_end = tempxend
-                y_end = tempystart
-
-            ## cropping is finished
-            cv2.rectangle(image, (x_start, y_start), (x_end, y_end), (255, 0, 0), 2)
-            cropping = False
-
-            refPoint = [(x_start, y_start), (x_end, y_end)]
-
-            if len(refPoint) == 2:  # when two points were found
-                image_data_real_cropped = image_data_real[refPoint[0][1] * 8:refPoint[1][1] * 8,
-                                          refPoint[0][0] * 8:refPoint[1][0] * 8]
-                image_data_imag_cropped = image_data_imag[refPoint[0][1] * 8:refPoint[1][1] * 8,
-                                          refPoint[0][0] * 8:refPoint[1][0] * 8]
-
-                roi = oriImage[refPoint[0][1] * 8:refPoint[1][1] * 8, refPoint[0][0] * 8:refPoint[1][0] * 8]
-                roi = cv2.resize(roi, (8 * (x_end - x_start), 8 * (y_end - y_start)))
-
-                cv2.imwrite(destination_directory + '\\cropped_npy_to_png.png', roi)
-                cv2.imshow("Cropped", roi)
-                cropped_img_png = Image.open(destination_directory + '\\cropped_npy_to_png.png')
-                numpy_crop = asarray(cropped_img_png)
-                np.save(destination_directory + '\\cropped.npy', numpy_crop)
-                np.save(test_data + '\\image_data_real_cropped.npy', image_data_real_cropped)
-                np.save(test_data + '\\image_data_imag_cropped.npy', image_data_imag_cropped)
-
-    h, w, c = image_png.shape
-    # resizing image
-    image = cv2.resize(image_png, (int(w / 8), int(h / 8)))
-    cv2.namedWindow("image")
-    cv2.setMouseCallback("image", mouse_crop)
-
-    while True:
-        i = image.copy()
-
-        if not cropping:
-            cv2.imshow("image", image)
-
-        elif cropping:
-            cv2.imshow("image", i)
-            cv2.rectangle(i, (x_start, y_start), (x_end, y_end), (255, 0, 0), 2)
-
-        key = cv2.waitKey(10)
-        if key == ord('q'):
-            cv2.destroyAllWindows()
-            return
-
-
-def get_info_image(image_path, destination_directory):
-    """ A function for retrieving informations on the CoSar stored image such as its png equivalent, its real and
-        imaginary part and the threshold to be applied later
-
-            Parameters
-            ----------
-            image_path: string
-            the path leading to image in CoSar format
-
-            destination_directory: string
-            the path for saving results in
-
-            Returns
-            ----------
-            None
-
-    """
-
-    filename, file_extension = os.path.splitext(image_path)
-    if file_extension == ".npy":
-        image_data = np.load(image_path)
-
-    else:
-        image_data = cos2mat(image_path)
-
-    # GET THE TWO PARTS
-    image_data_real = image_data[:, :, 0]
-    image_data_imag = image_data[:, :, 1]
-
-    # GET NOISY FOR THRESHOLD
-    image = np.squeeze(np.sqrt(np.square(image_data_real) + np.square(image_data_imag)))
-    threshold = np.mean(image) + 3 * np.std(image)
-
-    # DISPLAY FULL PICTURE
-    filename = store_data_and_plot(image, threshold, destination_directory + '\\image_provided.npy')
-    print('full picture in png is saved')
-    image_png = cv2.imread(filename.replace('npy', 'png'))
-    print('full picture in png has a dimension of {size}'.format(size=image_png.shape))
-    return image_png, image_data, image_data_real, image_data_imag, threshold, filename
diff --git a/deepdespeckling/sar2sar/test/__init__.py b/deepdespeckling/sar2sar/test/__init__.py
deleted file mode 100644
index cc2be44..0000000
--- a/deepdespeckling/sar2sar/test/__init__.py
+++ /dev/null
@@ -1,11 +0,0 @@
-"""Top-level package for merlin."""
-
-__author__ = """youcef kemiche"""
-__email__ = 'y.kemiche06@hotmail.com'
-__version__ = '0.1.0'
-
-from merlinsar.test.utils import *
-from merlinsar.test.load_cosar import *
-from merlinsar.test.model import *
-from merlinsar.test.model_test import *
-from merlinsar.test.spotlight import*
\ No newline at end of file
diff --git a/deepdespeckling/sar2sar/test/load_cosar.py b/deepdespeckling/sar2sar/test/load_cosar.py
deleted file mode 100644
index f3da7d9..0000000
--- a/deepdespeckling/sar2sar/test/load_cosar.py
+++ /dev/null
@@ -1,46 +0,0 @@
-import struct
-import numpy as np
-
-def cos2mat(imgName):
-    print('Converting CoSAR to numpy array of size [ncolumns,nlines,2]')
-
-    try:
-        fin = open(imgName, 'rb');
-    except IOError:
-        legx = imgName + ': it is a not openable file'
-        print(legx)
-        print(u'failed to call cos2mat')
-        return 0, 0, 0, 0
-
-    ibib = struct.unpack(">i", fin.read(4))[0]
-    irsri = struct.unpack(">i", fin.read(4))[0]
-    irs = struct.unpack(">i", fin.read(4))[0]
-    ias = struct.unpack(">i", fin.read(4))[0]
-    ibi = struct.unpack(">i", fin.read(4))[0]
-    irtnb = struct.unpack(">i", fin.read(4))[0]
-    itnl = struct.unpack(">i", fin.read(4))[0]
-
-    nlig = struct.unpack(">i", fin.read(4))[0]
-    ncoltot = int(irtnb / 4)
-    ncol = ncoltot - 2
-    nlig = ias
-
-    print(u'Reading image in CoSAR format.  ncolumns=%d  nlines=%d' % (ncol, nlig))
-
-    firm = np.zeros(4 * ncoltot, dtype=np.byte())
-    imgcxs = np.empty([nlig, ncol], dtype=np.complex64())
-
-    fin.seek(0)
-    firm = fin.read(4 * ncoltot)
-    firm = fin.read(4 * ncoltot)
-    firm = fin.read(4 * ncoltot)
-    firm = fin.read(4 * ncoltot)
-    #
-    for iut in range(nlig):
-        firm = fin.read(4 * ncoltot)
-        imgligne = np.ndarray(2 * ncoltot, '>h', firm)
-        imgcxs[iut, :] = imgligne[4:2 * ncoltot:2] + 1j * imgligne[5:2 * ncoltot:2]
-
-    print('[:,:,0] contains the real part of the SLC image data')
-    print('[:,:,1] contains the imaginary part of the SLC image data')
-    return np.stack((np.real(imgcxs), np.imag(imgcxs)), axis=2)
\ No newline at end of file
diff --git a/deepdespeckling/sar2sar/test/model_test.py b/deepdespeckling/sar2sar/test/model_test.py
deleted file mode 100644
index 6324592..0000000
--- a/deepdespeckling/sar2sar/test/model_test.py
+++ /dev/null
@@ -1,166 +0,0 @@
-from merlinsar.test.utils import *
-from merlinsar.test.model import *
-import torch
-import numpy as np
-from tqdm import tqdm 
-
-M = 10.089038980848645
-m = -1.429329123112601
-
-class Denoiser(object):
-    """ Description
-                ----------
-                A set of initial conditions, and transformations on the Y
-
-                Parameters
-                ----------
-                denoiser : an object
-
-                Returns
-                ----------
-    """
-
-    
-    def __init__(self, input_c_dim=1):
-
-        self.input_c_dim = input_c_dim
-
-
-    def load(self,model,weights_path):
-        """ Description
-                    ----------
-                    Restores a checkpoint located in a checkpoint repository
-
-                    Parameters
-                    ----------
-                    checkpoint_dir : a path leading to the checkpoint file
-
-                    Returns
-                    ----------
-                    True : Restoration is a success
-                    False: Restoration has failed
-        """
-        print("[*] Loading the model...")
-
-        model.load_state_dict(torch.load(weights_path))
-
-        return model
-
-     
-    def test(self,test_files, weights_path, save_dir,stride,patch_size,height,width):
-
-        """ Description
-                    ----------
-                    The function that does the job. Should be merged with main.py ?
-
-                    Parameters
-                    ----------
-                    test_files : a path leading to the checkpoint file
-                    ckpt_dir : repository containing the checkpoint (and weights)
-                    save_dir : repository to save sar images, real images and noisy images
-                    dataset_dir : the path to the test data
-                    stride : number of bytes from one row of pixels in memory to the next row of pixels in memory
-
-                    Returns
-                    ----------
-                    True : Restoration is a success
-                    False: Restoration has failed
-
-        """
-
-        """Test MERLIN"""
-
-        assert len(test_files) != 0, 'No testing data!'
-
-        loaded_model = Model(height,width,torch.device("cuda:0" if torch.cuda.is_available() else "cpu"))
-        loaded_model.load_state_dict(torch.load(weights_path, map_location=torch.device('cpu')))
-
-        # loaded_model = self.load(model,weights_path)
-        print()        
-        print(" [*] Load weights SUCCESS...")
-        print("[*] start testing...")
-        
-        for idx in range(len(test_files)):
-            real_image = load_sar_images(test_files[idx]).astype(np.float32)
-            i_real_part = (real_image[:, :, :, 0]).reshape(real_image.shape[0], real_image.shape[1],
-                                                           real_image.shape[2], 1)
-            i_imag_part = (real_image[:, :, :, 1]).reshape(real_image.shape[0], real_image.shape[1],
-                                                           real_image.shape[2], 1)
-
-
-    
-            # Pad the image
-            im_h = np.size(real_image, 1)
-            im_w = np.size(real_image, 2)
-
-            count_image = np.zeros(i_real_part.shape)
-            output_clean_image_1 = np.zeros(i_real_part.shape)
-            output_clean_image_2 = np.zeros(i_real_part.shape)
-
-            if im_h == patch_size:
-                x_range = list(np.array([0]))
-
-            else:
-                x_range = list(range(0, im_h - patch_size, stride))
-                if (x_range[-1] + patch_size) < im_h: x_range.extend(range(im_h - patch_size, im_h - patch_size + 1))
-
-            if im_w == patch_size:
-                y_range = list(np.array([0]))
-
-            else:
-                y_range = list(range(0, im_w - patch_size, stride))
-                if (y_range[-1] + patch_size) < im_w: y_range.extend(range(im_w - patch_size, im_w - patch_size + 1))
-
-            for x in tqdm(x_range):
-                for y in y_range:
-                  
-                    real_to_denoise, imag_to_denoise = symetrisation_patch_test(i_real_part[:, x:x + patch_size, y:y + patch_size, :],i_imag_part[:, x:x + patch_size, y:y + patch_size, :])
-
-                    real_to_denoise =torch.tensor(real_to_denoise)  
-                    imag_to_denoise=torch.tensor(imag_to_denoise)   
-
-                    real_to_denoise = real_to_denoise.type(torch.float32)
-                    imag_to_denoise = imag_to_denoise.type(torch.float32)
-
-                    real_to_denoise=(torch.log(torch.square(real_to_denoise)+1e-3)-2*m)/(2*(M-m))
-                    imag_to_denoise=(torch.log(torch.square(imag_to_denoise)+1e-3)-2*m)/(2*(M-m))
-
-            
-                    tmp_clean_image_real = loaded_model.forward(real_to_denoise).detach().numpy()                          
-                    tmp_clean_image_real=np.moveaxis(tmp_clean_image_real, 1, -1)
-
-                    output_clean_image_1[:, x:x + patch_size, y:y + patch_size, :] = output_clean_image_1[:, x:x + patch_size,
-                                                                                 y:y + patch_size,
-                                                                                 :] + tmp_clean_image_real
-
-
-
-                    tmp_clean_image_imag = loaded_model.forward(imag_to_denoise).detach().numpy()
-                    tmp_clean_image_imag=np.moveaxis(tmp_clean_image_imag, 1, -1)
-
-                    output_clean_image_2[:, x:x + patch_size, y:y + patch_size, :] = output_clean_image_2[:, x:x + patch_size,
-                                                                                 y:y + patch_size,
-                                                                                 :] + tmp_clean_image_imag
-                    count_image[:, x:x + patch_size, y:y + patch_size, :] = count_image[:, x:x + patch_size, y:y + patch_size,
-                                                                        :] + np.ones((1, patch_size, patch_size, 1))
-
-
-            output_clean_image_1 = output_clean_image_1 / count_image
-            output_clean_image_2 = output_clean_image_2 / count_image
-            output_clean_image = 0.5 * (np.square(denormalize_sar(output_clean_image_1)) + np.square(
-            denormalize_sar(output_clean_image_2))) # combine the two estimation
-
-
-            noisyimage = np.squeeze(np.sqrt(i_real_part ** 2 + i_imag_part ** 2))
-            outputimage = np.sqrt(np.squeeze(output_clean_image))
-
-            # imagename = test_files[idx].replace(dataset_dir, "") test_image_data
-            imagename = "test_image_data.npy"
-
-            print("Denoised image %s" % imagename)
-
-            save_sar_images(outputimage, noisyimage, imagename, save_dir)
-            save_real_imag_images( denormalize_sar(output_clean_image_1), denormalize_sar(output_clean_image_2),
-                                  imagename, save_dir)
-                                  
-            save_real_imag_images_noisy( np.squeeze(i_real_part), np.squeeze(i_imag_part), imagename, save_dir)
\ No newline at end of file
diff --git a/deepdespeckling/sar2sar/test/results/denoised_imag_test_image_data.png b/deepdespeckling/sar2sar/test/results/denoised_imag_test_image_data.png
deleted file mode 100644
index 8aa904a..0000000
Binary files a/deepdespeckling/sar2sar/test/results/denoised_imag_test_image_data.png and /dev/null differ
diff --git a/deepdespeckling/sar2sar/test/saved_model/model.pth b/deepdespeckling/sar2sar/test/saved_model/model.pth
deleted file mode 100644
index 827285b..0000000
Binary files a/deepdespeckling/sar2sar/test/saved_model/model.pth and /dev/null differ
diff --git a/deepdespeckling/sar2sar/test/spotlight.py b/deepdespeckling/sar2sar/test/spotlight.py
deleted file mode 100644
index 226f28f..0000000
--- a/deepdespeckling/sar2sar/test/spotlight.py
+++ /dev/null
@@ -1,159 +0,0 @@
-import torch
-
-from merlinsar.test.model import *
-from merlinsar.test.utils import *
-from merlinsar.test.model_test import * 
-import os
-from glob import glob
-
-import numpy as np
-from merlinsar.test.load_cosar import cos2mat
-
-M = 10.089038980848645
-m = -1.429329123112601
-
-this_dir, this_filename = os.path.split(__file__)
-
-
-def despeckle(image_path,destination_directory,stride_size=64,
-                model_weights_path= os.path.join(this_dir, "saved_model", "model.pth"),patch_size=256,height=256,width=256):
-
-    """ Description
-            ----------
-            Runs a test instance by calling the test function defined in model.py on a few samples
-
-            Parameters
-            ----------
-            denoiser : an object
-
-            Returns
-            ----------
-
-    """
-
-    denoiser=Denoiser()
-
-    if not os.path.exists(destination_directory+'/processed_image'):
-        os.mkdir(destination_directory+'/processed_image')
-    
-    test_data=destination_directory+'/processed_image'
-
-    filelist = glob(os.path.join(test_data, "*"))
-    for f in filelist:
-        os.remove(f)
-
-    image_data = cos2mat(image_path) 
-
-    np.save(test_data+'/test_image_data.npy',image_data)
-
-    print(
-        "[*] Start testing on real data. Working directory: %s. Collecting data from %s and storing test results in %s" % (
-            os.getcwd(), destination_directory, destination_directory))
-            
-    test_files = glob((test_data + '/*.npy'))
-    print(test_files)
-
-    denoiser.test(test_files,model_weights_path, save_dir=destination_directory,
-                  stride=stride_size,patch_size=patch_size,height=height,width=width)
-        
-
-def despeckle_from_coordinates(image_path,coordinates_dict,destination_directory,stride_size=64,
-                model_weights_path= os.path.join(this_dir, "saved_model", "model.pth"),patch_size=256,height=256,width=256):
-    """ Description
-            ----------
-            Runs a test instance by calling the test function defined in model.py on a few samples
-
-            Parameters
-            ----------
-            denoiser : an object
-
-            Returns
-            ----------
-
-    """
-
-    x_start=coordinates_dict["x_start"]
-    x_end=coordinates_dict["x_end"]
-    y_start=coordinates_dict["y_start"]
-    y_end=coordinates_dict["y_end"]
-
-    denoiser=Denoiser()
-
-    if not os.path.exists(destination_directory+'/processed_image'):
-        os.mkdir(destination_directory+'/processed_image')
-    
-    test_data=destination_directory+'/processed_image'
- 
-    filelist = glob(os.path.join(test_data, "*"))
-    for f in filelist:
-        os.remove(f)
-
-    image_data = cos2mat(image_path) 
-
-    np.save(test_data+'/test_image_data.npy',image_data[x_start:x_end,y_start:y_end,:])
-
-    print(
-        "[*] Start testing on real data. Working directory: %s. Collecting data from %s and storing test results in %s" % (
-            os.getcwd(), destination_directory, destination_directory))
-            
-    test_files = glob((test_data + '/*.npy'))
-    print(test_files)
-    denoiser.test(test_files,model_weights_path, save_dir=destination_directory,
-                  stride=stride_size,patch_size=patch_size,height=height,width=width)
-
-
-
-def despeckle_from_crop(image_path,destination_directory,stride_size=64,
-                model_weights_path= os.path.join(this_dir, "saved_model", "model.pth"),patch_size=256,height=256,width=256):
-    """ Description
-            ----------
-            Runs a test instance by calling the test function defined in model.py on a few samples
-
-            Parameters
-            ----------
-            denoiser : an object
-
-            Returns
-            ----------
-
-    """
-
-    denoiser=Denoiser()
-
-    if not os.path.exists(destination_directory+'\\processed_image'):
-        os.mkdir(destination_directory+'\\processed_image')
-
-    test_data=destination_directory+'\\processed_image'
-
-    filelist = glob(os.path.join(test_data, "*"))
-    for f in filelist:
-        os.remove(f)
-
-
-    # FROM IMAGE PATH RETRIEVE PNG, NPY, REAL , IMAG, THRESHOLD, FILENAME
-    image_png , image_data, image_data_real , image_data_imag, threshold, filename = get_info_image(image_path,destination_directory)
-
-    # CROPPING OUR PNG AND REFLECT THE CROP ON REAL AND IMAG
-    cropping = False
-    crop(image_png,image_data_real, image_data_imag, destination_directory,test_data,cropping)
-
-
-    image_data_real_cropped = np.load(test_data+'\\image_data_real_cropped.npy')
-    store_data_and_plot(image_data_real_cropped, threshold, test_data+'\\image_data_real_cropped.npy')
-    image_data_imag_cropped =np.load(test_data+'\\image_data_imag_cropped.npy')
-    store_data_and_plot(image_data_imag_cropped, threshold, test_data+'\\image_data_imag_cropped.npy')
-
-    image_data_real_cropped= image_data_real_cropped.reshape(image_data_real_cropped.shape[0],image_data_real_cropped.shape[1],1)
-    image_data_imag_cropped = image_data_imag_cropped.reshape(image_data_imag_cropped.shape[0],
-                                                              image_data_imag_cropped.shape[1], 1)
-
-    np.save(test_data + '/test_image_data_cropped.npy', np.concatenate((image_data_real_cropped, image_data_imag_cropped), axis=2))
-
-    print(
-        "[*] Start testing on real data. Working directory: %s. Collecting data from %s and storing test results in %s" % (
-            os.getcwd(), destination_directory, destination_directory))
-
-    test_files = glob((test_data +'/test_image_data_cropped.npy'))
-    print(test_files)
-    denoiser.test(test_files,model_weights_path, save_dir=destination_directory,
-                  stride=stride_size,patch_size=patch_size,height=height,width=width)
diff --git a/deepdespeckling/sar2sar/test/utils.py b/deepdespeckling/sar2sar/test/utils.py
deleted file mode 100644
index 8e16f73..0000000
--- a/deepdespeckling/sar2sar/test/utils.py
+++ /dev/null
@@ -1,325 +0,0 @@
-import numpy as np
-from PIL import Image
-from scipy import special
-from scipy import signal
-from merlinsar.test.load_cosar import cos2mat
-import cv2
-from PIL import Image
-import numpy as np
-from numpy import asarray
-
-
-# DEFINE PARAMETERS OF SPECKLE AND NORMALIZATION FACTOR
-M = 10.089038980848645
-m = -1.429329123112601
-L = 1
-c = (1 / 2) * (special.psi(L) - np.log(L))
-cn = c / (M - m)  # normalized (0,1) mean of log speckle
-
-
-def normalize_sar(im):
-    """ Description
-            ----------
-            Normalization of a numpy-stored image
-
-            Parameters
-            ----------
-            im : an image object
-
-            Returns
-            ----------
-
-        """
-    if not isinstance(im, np.ndarray):
-        raise TypeError('Please provide a .npy argument')
-    return ((np.log(im + np.spacing(1)) - m) * 255 / (M - m)).astype('float32')
-
-
-def denormalize_sar(im):
-    """ Description
-            ----------
-            Denormalization of a numpy image
-
-            Parameters
-            ----------
-            im : an image object
-
-            Returns
-            ----------
-
-        """
-    if not isinstance(im, np.ndarray):
-        raise TypeError('Please provide a .npy argument')
-    return np.exp((M - m) * (np.squeeze(im)).astype('float32') + m)
-
-
-def symetrisation_patch_test(real_part, imag_part):
-    if not isinstance(real_part, np.ndarray):
-        raise TypeError('Please provide a .npy argument')
-    if not isinstance(imag_part, np.ndarray):
-        raise TypeError('Please provide a .npy argument')
-    S = np.fft.fftshift(np.fft.fft2(real_part[0, :, :, 0] + 1j * imag_part[0, :, :, 0]))
-    p = np.zeros((S.shape[0]))  # azimut (ncol)
-    for i in range(S.shape[0]):
-        p[i] = np.mean(np.abs(S[i, :]))
-    sp = p[::-1]
-    c = np.real(np.fft.ifft(np.fft.fft(p) * np.conjugate(np.fft.fft(sp))))
-    d1 = np.unravel_index(c.argmax(), p.shape[0])
-    d1 = d1[0]
-    shift_az_1 = int(round(-(d1 - 1) / 2)) % p.shape[0] + int(p.shape[0] / 2)
-    p2_1 = np.roll(p, shift_az_1)
-    shift_az_2 = int(round(-(d1 - 1 - p.shape[0]) / 2)) % p.shape[0] + int(p.shape[0] / 2)
-    p2_2 = np.roll(p, shift_az_2)
-    window = signal.gaussian(p.shape[0], std=0.2 * p.shape[0])
-    test_1 = np.sum(window * p2_1)
-    test_2 = np.sum(window * p2_2)
-    # make sure the spectrum is symetrized and zeo-Doppler centered
-    if test_1 >= test_2:
-        p2 = p2_1
-        shift_az = shift_az_1 / p.shape[0]
-    else:
-        p2 = p2_2
-        shift_az = shift_az_2 / p.shape[0]
-    S2 = np.roll(S, int(shift_az * p.shape[0]), axis=0)
-
-    q = np.zeros((S.shape[1]))  # range (nlin)
-    for j in range(S.shape[1]):
-        q[j] = np.mean(np.abs(S[:, j]))
-    sq = q[::-1]
-    # correlation
-    cq = np.real(np.fft.ifft(np.fft.fft(q) * np.conjugate(np.fft.fft(sq))))
-    d2 = np.unravel_index(cq.argmax(), q.shape[0])
-    d2 = d2[0]
-    shift_range_1 = int(round(-(d2 - 1) / 2)) % q.shape[0] + int(q.shape[0] / 2)
-    q2_1 = np.roll(q, shift_range_1)
-    shift_range_2 = int(round(-(d2 - 1 - q.shape[0]) / 2)) % q.shape[0] + int(q.shape[0] / 2)
-    q2_2 = np.roll(q, shift_range_2)
-    window_r = signal.gaussian(q.shape[0], std=0.2 * q.shape[0])
-    test_1 = np.sum(window_r * q2_1)
-    test_2 = np.sum(window_r * q2_2)
-    if test_1 >= test_2:
-        q2 = q2_1
-        shift_range = shift_range_1 / q.shape[0]
-    else:
-        q2 = q2_2
-        shift_range = shift_range_2 / q.shape[0]
-
-    Sf = np.roll(S2, int(shift_range * q.shape[0]), axis=1)
-    ima2 = np.fft.ifft2(np.fft.ifftshift(Sf))
-    ima2 = ima2.reshape(1, np.size(ima2, 0), np.size(ima2, 1), 1)
-    return np.real(ima2), np.imag(ima2)
-
-
-def load_sar_images(filelist):
-    """ Description
-            ----------
-            Loads files , resize them and append them into a list called data
-
-            Parameters
-            ----------
-            filelist : a path to a folder containing the images
-
-            Returns
-            ----------
-            A list of images
-
-        """
-    if not isinstance(filelist, list):
-        im = np.load(filelist)
-        return np.array(im).reshape(1, np.size(im, 0), np.size(im, 1), 2)
-    data = []
-    for file in filelist:
-        im = np.load(file)
-        data.append(np.array(im).reshape(1, np.size(im, 0), np.size(im, 1), 2))
-    return data
-
-
-def store_data_and_plot(im, threshold, filename):
-    """ Description
-            ----------
-            Creates an image memory from an object exporting the array interface and returns a
-            converted copy of this image into greyscale mode ("L")
-
-            However, there is not plotting functions' call ?
-
-            Parameters
-            ----------
-            im : the image to store
-            threshold: clip a maximum value in the image array i.e values are to be between 0 and threshold
-            filename: the path to store the result array image in .png
-
-            Returns
-            ----------
-            None
-
-        """
-    if not isinstance(im, np.ndarray):
-        raise TypeError('Please provide a .npy argument')
-    im = np.clip(im, 0, threshold)
-
-    im = im / threshold * 255
-    im = Image.fromarray(im.astype('float64')).convert('L')
-    im.save(filename.replace('npy', 'png'))
-    return filename
-
-def save_sar_images(denoised, noisy, imagename, save_dir, groundtruth=None):
-    choices = {'marais1': 190.92, 'marais2': 168.49, 'saclay': 470.92, 'lely': 235.90, 'ramb': 167.22,
-               'risoul': 306.94, 'limagne': 178.43, 'saintgervais': 560, 'Serreponcon': 450.0,
-               'Sendai': 600.0, 'Paris': 1291.0, 'Berlin': 1036.0, 'Bergen': 553.71,
-               'SDP_Lambesc': 349.53, 'Grand_Canyon': 287.0, 'domancy': 560, 'Brazil': 103.0}
-    threshold = None
-    for x in choices:
-        if x in imagename:
-            threshold = choices.get(x)
-    if threshold is None: threshold = np.mean(noisy) + 3 * np.std(noisy)
-
-    ####
-    imagename = imagename.split('\\')[-1]
-    ####
-
-    if groundtruth:
-        groundtruthfilename = save_dir + "/groundtruth_" + imagename
-        np.save(groundtruthfilename, groundtruth)
-        store_data_and_plot(groundtruth, threshold, groundtruthfilename)
-
-    denoisedfilename = save_dir + "/denoised_" + imagename
-    np.save(denoisedfilename, denoised)
-    store_data_and_plot(denoised, threshold, denoisedfilename)
-
-    noisyfilename = save_dir + "/noisy_" + imagename
-    np.save(noisyfilename, noisy)
-    store_data_and_plot(noisy, threshold, noisyfilename)
-
-
-def save_real_imag_images(real_part, imag_part, imagename, save_dir):
-    choices = {'marais1': 190.92, 'marais2': 168.49, 'saclay': 470.92, 'lely': 235.90, 'ramb': 167.22,
-               'risoul': 306.94, 'limagne': 178.43, 'saintgervais': 560, 'Serreponcon': 450.0,
-               'Sendai': 600.0, 'Paris': 1291.0, 'Berlin': 1036.0, 'Bergen': 553.71,
-               'SDP_Lambesc': 349.53, 'Grand_Canyon': 287.0, 'Brazil': 103.0}
-    threshold = None
-    for x in choices:
-        if x in imagename:
-            threshold = choices.get(x)
-    if threshold is None: threshold = np.mean(imag_part) + 3 * np.std(imag_part)
-
-    ####
-    imagename = imagename.split('\\')[-1]
-    ####
-
-    realfilename = save_dir + "/denoised_real_" + imagename
-    np.save(realfilename, real_part)
-    store_data_and_plot(real_part, threshold, realfilename)
-
-    imagfilename = save_dir + "/denoised_imag_" + imagename
-    np.save(imagfilename, imag_part)
-    store_data_and_plot(imag_part, threshold, imagfilename)
-
-
-def save_real_imag_images_noisy(real_part, imag_part, imagename, save_dir):
-    choices = {'marais1': 190.92, 'marais2': 168.49, 'saclay': 470.92, 'lely': 235.90, 'ramb': 167.22,
-               'risoul': 306.94, 'limagne': 178.43, 'saintgervais': 560, 'Serreponcon': 450.0,
-               'Sendai': 600.0, 'Paris': 1291.0, 'Berlin': 1036.0, 'Bergen': 553.71,
-               'SDP_Lambesc': 349.53, 'Grand_Canyon': 287.0, 'Brazil': 103.0}
-    threshold = None
-    for x in choices:
-        if x in imagename:
-            threshold = choices.get(x)
-    if threshold is None: threshold = np.mean(np.abs(imag_part)) + 3 * np.std(np.abs(imag_part))
-
-    ####
-    imagename = imagename.split('\\')[-1]
-    ####
-
-    realfilename = save_dir + "/noisy_real_" + imagename
-    np.save(realfilename, real_part)
-    store_data_and_plot(np.sqrt(2) * np.abs(real_part), threshold, realfilename)
-
-    imagfilename = save_dir + "/noisy_imag_" + imagename
-    np.save(imagfilename, imag_part)
-    store_data_and_plot(np.sqrt(2) * np.abs(imag_part), threshold, imagfilename)
-
-def cal_psnr(Shat, S):
-    # takes amplitudes in input
-    # Shat: a SAR amplitude image
-    # S:    a reference SAR image
-    P = np.quantile(S, 0.99)
-    res = 10 * np.log10((P ** 2) / np.mean(np.abs(Shat - S) ** 2))
-    return res
-
-def crop(image_png,image_data_real, image_data_imag,destination_directory,test_data, cropping):
-    # HERE I READ THE PNG FILE
-    oriImage = image_png.copy()
-    cropping = False
-    x_start, y_start, x_end, y_end = 0, 0, 0, 0
-
-    # CV2 CROPPING IN WINDOW
-    def mouse_crop(event, x, y, flags, param):
-        global x_start, y_start, x_end, y_end, cropping
-        cropping = False
-        if event == cv2.EVENT_LBUTTONDOWN:
-            x_start, y_start, x_end, y_end = x, y, x, y
-            cropping = True
-
-        # Mouse is Moving
-        elif event == cv2.EVENT_MOUSEMOVE:
-            cropping = True
-            if cropping:
-                cv2.rectangle(image, (x_start, y_start), (x_start + 32, y_start + 32), (255, 0, 0), 2)
-                x_end, y_end = x, y
-
-        # if the left mouse button was released
-        elif event == cv2.EVENT_LBUTTONUP:
-            # record the ending (x, y) coordinates
-            x_end, y_end = x_start + 32, y_start + 32
-            cropping = False  # cropping is finished
-
-            refPoint = [(x_start, y_start), (x_end, y_end)]
-
-            if len(refPoint) == 2:  # when two points were found
-                image_data_real_cropped = image_data_real[refPoint[0][1] * 8:refPoint[1][1] * 8, refPoint[0][0] * 8:refPoint[1][0] * 8]
-                image_data_imag_cropped = image_data_imag[refPoint[0][1] * 8:refPoint[1][1] * 8, refPoint[0][0] * 8:refPoint[1][0] * 8]
-                roi = oriImage[refPoint[0][1] * 8:refPoint[1][1] * 8, refPoint[0][0] * 8:refPoint[1][0] * 8]
-                roi = cv2.resize(roi, (256, 256))
-                cv2.imwrite(destination_directory+'\\cropped_npy_to_png.png', roi)
-                cv2.imshow("Cropped", roi)
-                cropped_img_png = Image.open(destination_directory+'\\cropped_npy_to_png.png')
-                numpy_crop = asarray(cropped_img_png)
-                np.save(destination_directory+'\\cropped.npy', numpy_crop)
-                np.save(test_data+'\\image_data_real_cropped.npy', image_data_real_cropped)
-                np.save(test_data + '\\image_data_imag_cropped.npy', image_data_imag_cropped)
-
-    h, w , c = image_png.shape
-    image = cv2.resize(image_png, (int(w / 8), int(h / 8)))
-    cv2.namedWindow("image")
-    cv2.setMouseCallback("image", mouse_crop)
-
-    while True:
-        i = image.copy()
-
-        if not cropping:
-            cv2.imshow("image", image)
-
-        elif cropping:
-            cv2.imshow("image", i)
-
-        key = cv2.waitKey(10)
-        if key == ord('q'):
-            return
-
-def get_info_image(image_path,destination_directory):
-    image_data = cos2mat(image_path)
-
-    # GET THE TWO PARTS
-    image_data_real = image_data[:, :, 0]
-    image_data_imag = image_data[:, :, 1]
-
-    # GET NOISY FOR THRESHOLD
-    image = np.squeeze(np.sqrt(np.square(image_data_real) + np.square(image_data_imag)))
-    threshold = np.mean(image) + 3 * np.std(image)
-
-    # DISPLAY FULL PICTURE
-    filename = store_data_and_plot(image, threshold, destination_directory + '\\image_provided.npy')
-    print('full picture in png is saved')
-    image_png = cv2.imread(filename.replace('npy', 'png'))
-    print('full picture in png has a dimension of {size}'.format(size=image_png.shape))
-    return image_png , image_data, image_data_real , image_data_imag, threshold, filename
diff --git a/deepdespeckling/test_deepdespeckling.py b/deepdespeckling/test_deepdespeckling.py
new file mode 100644
index 0000000..71529c4
--- /dev/null
+++ b/deepdespeckling/test_deepdespeckling.py
@@ -0,0 +1,9 @@
+from deepdespeckling.despeckling import despeckle, despeckle_from_coordinates, despeckle_from_crop
+
+image_path = "/Users/hadrienmariaccia/Documents/Projects/deepdespeckling/img/entire/sar2sar_tests"
+destination_directory = "/Users/hadrienmariaccia/Documents/Projects/deepdespeckling/img/entire/sar2sar_tests"
+coordinates_dictionnary = {'x_start': 0,
+                           'y_start': 0, 'x_end': 400, 'y_end': 400}
+
+despeckle(image_path, destination_directory,
+          model_name="sar2sar")
diff --git a/tests/merlin/test/test_utils.py b/tests/merlin/test/test_utils.py
deleted file mode 100644
index 14eff69..0000000
--- a/tests/merlin/test/test_utils.py
+++ /dev/null
@@ -1,450 +0,0 @@
-import numpy as np
-from PIL import Image
-from scipy import special
-from scipy import signal
-from deepdespeckling.merlin.test.load_cosar import cos2mat
-import cv2
-from PIL import Image
-import numpy as np
-from numpy import asarray
-
-
-# DEFINE PARAMETERS OF SPECKLE AND NORMALIZATION FACTOR
-M = 10.089038980848645
-m = -1.429329123112601
-L = 1
-c = (1 / 2) * (special.psi(L) - np.log(L))
-cn = c / (M - m)  # normalized (0,1) mean of log speckle
-
-
-def normalize_sar(im):
-    """ Description
-            ----------
-            Normalization of a numpy-stored image
-
-            Parameters
-            ----------
-            im : an image object
-
-            Returns
-            ----------
-
-        """
-    if not isinstance(im, np.ndarray):
-        raise TypeError('Please provide a .npy argument')
-    return ((np.log(im + np.spacing(1)) - m) * 255 / (M - m)).astype('float32')
-
-
-def denormalize_sar(im):
-    """ Description
-            ----------
-            Denormalization of a numpy image
-
-            Parameters
-            ----------
-            im : an image object
-
-            Returns
-            ----------
-
-        """
-    if not isinstance(im, np.ndarray):
-        raise TypeError('Please provide a .npy argument')
-    return np.exp((M - m) * (np.squeeze(im)).astype('float32') + m)
-
-
-def symetrisation_patch_test(real_part, imag_part):
-    if not isinstance(real_part, np.ndarray):
-        raise TypeError('Please provide a .npy argument')
-    if not isinstance(imag_part, np.ndarray):
-        raise TypeError('Please provide a .npy argument')
-    S = np.fft.fftshift(np.fft.fft2(real_part[0, :, :, 0] + 1j * imag_part[0, :, :, 0]))
-    p = np.zeros((S.shape[0]))  # azimut (ncol)
-    for i in range(S.shape[0]):
-        p[i] = np.mean(np.abs(S[i, :]))
-    sp = p[::-1]
-    c = np.real(np.fft.ifft(np.fft.fft(p) * np.conjugate(np.fft.fft(sp))))
-    d1 = np.unravel_index(c.argmax(), p.shape[0])
-    d1 = d1[0]
-    shift_az_1 = int(round(-(d1 - 1) / 2)) % p.shape[0] + int(p.shape[0] / 2)
-    p2_1 = np.roll(p, shift_az_1)
-    shift_az_2 = int(round(-(d1 - 1 - p.shape[0]) / 2)) % p.shape[0] + int(p.shape[0] / 2)
-    p2_2 = np.roll(p, shift_az_2)
-    window = signal.gaussian(p.shape[0], std=0.2 * p.shape[0])
-    test_1 = np.sum(window * p2_1)
-    test_2 = np.sum(window * p2_2)
-    # make sure the spectrum is symetrized and zeo-Doppler centered
-    if test_1 >= test_2:
-        p2 = p2_1
-        shift_az = shift_az_1 / p.shape[0]
-    else:
-        p2 = p2_2
-        shift_az = shift_az_2 / p.shape[0]
-    S2 = np.roll(S, int(shift_az * p.shape[0]), axis=0)
-
-    q = np.zeros((S.shape[1]))  # range (nlin)
-    for j in range(S.shape[1]):
-        q[j] = np.mean(np.abs(S[:, j]))
-    sq = q[::-1]
-    # correlation
-    cq = np.real(np.fft.ifft(np.fft.fft(q) * np.conjugate(np.fft.fft(sq))))
-    d2 = np.unravel_index(cq.argmax(), q.shape[0])
-    d2 = d2[0]
-    shift_range_1 = int(round(-(d2 - 1) / 2)) % q.shape[0] + int(q.shape[0] / 2)
-    q2_1 = np.roll(q, shift_range_1)
-    shift_range_2 = int(round(-(d2 - 1 - q.shape[0]) / 2)) % q.shape[0] + int(q.shape[0] / 2)
-    q2_2 = np.roll(q, shift_range_2)
-    window_r = signal.gaussian(q.shape[0], std=0.2 * q.shape[0])
-    test_1 = np.sum(window_r * q2_1)
-    test_2 = np.sum(window_r * q2_2)
-    if test_1 >= test_2:
-        q2 = q2_1
-        shift_range = shift_range_1 / q.shape[0]
-    else:
-        q2 = q2_2
-        shift_range = shift_range_2 / q.shape[0]
-
-    Sf = np.roll(S2, int(shift_range * q.shape[0]), axis=1)
-    ima2 = np.fft.ifft2(np.fft.ifftshift(Sf))
-    ima2 = ima2.reshape(1, np.size(ima2, 0), np.size(ima2, 1), 1)
-    return np.real(ima2), np.imag(ima2)
-
-
-def load_sar_images(filelist):
-    """ Description
-            ----------
-            Loads files , resize them and append them into a list called data
-
-            Parameters
-            ----------
-            filelist : a path to a folder containing the images
-
-            Returns
-            ----------
-            A list of images
-
-        """
-    if not isinstance(filelist, list):
-        im = np.load(filelist)
-        return np.array(im).reshape(1, np.size(im, 0), np.size(im, 1), 2)
-    data = []
-    for file in filelist:
-        im = np.load(file)
-        data.append(np.array(im).reshape(1, np.size(im, 0), np.size(im, 1), 2))
-    return data
-
-
-def store_data_and_plot(im, threshold, filename):
-    """ Description
-            ----------
-            Creates an image memory from an object exporting the array interface and returns a
-            converted copy of this image into greyscale mode ("L")
-
-            However, there is not plotting functions' call ?
-
-            Parameters
-            ----------
-            im : the image to store
-            threshold: clip a maximum value in the image array i.e values are to be between 0 and threshold
-            filename: the path to store the result array image in .png
-
-            Returns
-            ----------
-            None
-
-        """
-    if not isinstance(im, np.ndarray):
-        raise TypeError('Please provide a .npy argument')
-    im = np.clip(im, 0, threshold)
-
-    im = im / threshold * 255
-    im = Image.fromarray(im.astype('float64')).convert('L')
-    im.save(filename.replace('npy', 'png'))
-    return filename
-
-def save_sar_images(denoised, noisy, imagename, save_dir, groundtruth=None):
-    choices = {'marais1': 190.92, 'marais2': 168.49, 'saclay': 470.92, 'lely': 235.90, 'ramb': 167.22,
-               'risoul': 306.94, 'limagne': 178.43, 'saintgervais': 560, 'Serreponcon': 450.0,
-               'Sendai': 600.0, 'Paris': 1291.0, 'Berlin': 1036.0, 'Bergen': 553.71,
-               'SDP_Lambesc': 349.53, 'Grand_Canyon': 287.0, 'domancy': 560, 'Brazil': 103.0}
-    threshold = None
-    for x in choices:
-        if x in imagename:
-            threshold = choices.get(x)
-    if threshold is None: threshold = np.mean(noisy) + 3 * np.std(noisy)
-
-    ####
-    imagename = imagename.split('\\')[-1]
-    ####
-
-    if groundtruth:
-        groundtruthfilename = save_dir + "/groundtruth_" + imagename
-        np.save(groundtruthfilename, groundtruth)
-        store_data_and_plot(groundtruth, threshold, groundtruthfilename)
-
-    denoisedfilename = save_dir + "/denoised_" + imagename
-    np.save(denoisedfilename, denoised)
-    store_data_and_plot(denoised, threshold, denoisedfilename)
-
-    noisyfilename = save_dir + "/noisy_" + imagename
-    np.save(noisyfilename, noisy)
-    store_data_and_plot(noisy, threshold, noisyfilename)
-
-
-def save_real_imag_images(real_part, imag_part, imagename, save_dir):
-    choices = {'marais1': 190.92, 'marais2': 168.49, 'saclay': 470.92, 'lely': 235.90, 'ramb': 167.22,
-               'risoul': 306.94, 'limagne': 178.43, 'saintgervais': 560, 'Serreponcon': 450.0,
-               'Sendai': 600.0, 'Paris': 1291.0, 'Berlin': 1036.0, 'Bergen': 553.71,
-               'SDP_Lambesc': 349.53, 'Grand_Canyon': 287.0, 'Brazil': 103.0}
-    threshold = None
-    for x in choices:
-        if x in imagename:
-            threshold = choices.get(x)
-    if threshold is None: threshold = np.mean(imag_part) + 3 * np.std(imag_part)
-
-    ####
-    imagename = imagename.split('\\')[-1]
-    ####
-
-    realfilename = save_dir + "/denoised_real_" + imagename
-    np.save(realfilename, real_part)
-    store_data_and_plot(real_part, threshold, realfilename)
-
-    imagfilename = save_dir + "/denoised_imag_" + imagename
-    np.save(imagfilename, imag_part)
-    store_data_and_plot(imag_part, threshold, imagfilename)
-
-
-def save_real_imag_images_noisy(real_part, imag_part, imagename, save_dir):
-    choices = {'marais1': 190.92, 'marais2': 168.49, 'saclay': 470.92, 'lely': 235.90, 'ramb': 167.22,
-               'risoul': 306.94, 'limagne': 178.43, 'saintgervais': 560, 'Serreponcon': 450.0,
-               'Sendai': 600.0, 'Paris': 1291.0, 'Berlin': 1036.0, 'Bergen': 553.71,
-               'SDP_Lambesc': 349.53, 'Grand_Canyon': 287.0, 'Brazil': 103.0}
-    threshold = None
-    for x in choices:
-        if x in imagename:
-            threshold = choices.get(x)
-    if threshold is None: threshold = np.mean(np.abs(imag_part)) + 3 * np.std(np.abs(imag_part))
-
-    ####
-    imagename = imagename.split('\\')[-1]
-    ####
-
-    realfilename = save_dir + "/noisy_real_" + imagename
-    np.save(realfilename, real_part)
-    store_data_and_plot(np.sqrt(2) * np.abs(real_part), threshold, realfilename)
-
-    imagfilename = save_dir + "/noisy_imag_" + imagename
-    np.save(imagfilename, imag_part)
-    store_data_and_plot(np.sqrt(2) * np.abs(imag_part), threshold, imagfilename)
-
-def cal_psnr(Shat, S):
-    # takes amplitudes in input
-    # Shat: a SAR amplitude image
-    # S:    a reference SAR image
-    P = np.quantile(S, 0.99)
-    res = 10 * np.log10((P ** 2) / np.mean(np.abs(Shat - S) ** 2))
-    return res
-
-def crop(image_png, image_data_real, image_data_imag, destination_directory, test_data):
-    """ A crapping tool for despeckling only the selection of the user, made with OpenCV
-            Parameters
-            ----------
-            image_png: .png file
-            the image to be cropped in png format
-            image_data_real: nd.array
-            the real part of the image stored in an array
-            image_data_imag: nd.array
-            the imaginary part of the image stored in an array
-            destination_directory: string
-            the path for saving results in
-            test_data: string
-            the path for saving results in
-            cropping: bool
-            A boolean stating if the user wants to crop the image or not
-            Returns
-            ----------
-            None
-        """
-    # HERE I READ THE PNG FILE
-    oriImage = image_png.copy()
-    cropping = False
-    x_start, y_start, x_end, y_end = 0, 0, 0, 0
-
-    # CV2 CROPPING IN WINDOW
-    def mouse_crop(event, x, y, flags, param):
-        """ The callback function of crop() to deal with user's events
-        """
-        global x_start, y_start, x_end, y_end, cropping
-        cropping = False
-
-        if event == cv2.EVENT_LBUTTONDOWN:
-            x_start, y_start, x_end, y_end = x, y, x, y
-            cropping = True
-
-        # Mouse is Moving
-        elif event == cv2.EVENT_MOUSEMOVE:
-            x_end, y_end = x, y
-
-        # if the left mouse button was released
-        elif event == cv2.EVENT_LBUTTONUP:
-            # record the ending (x, y) coordinates
-            x_end, y_end = x, y
-            # cropping is finished
-            cv2.rectangle(image, (x_start, y_start), (x, y), (255, 0, 0), 2)
-            cropping = False
-
-            refPoint = [(x_start, y_start), (x_end, y_end)]
-
-            if len(refPoint) == 2:  # when two points were found
-                image_data_real_cropped = image_data_real[refPoint[0][1] * 8:refPoint[1][1] * 8,
-                                          refPoint[0][0] * 8:refPoint[1][0] * 8]
-                image_data_imag_cropped = image_data_imag[refPoint[0][1] * 8:refPoint[1][1] * 8,
-                                          refPoint[0][0] * 8:refPoint[1][0] * 8]
-
-                roi = oriImage[refPoint[0][1] * 8:refPoint[1][1] * 8, refPoint[0][0] * 8:refPoint[1][0] * 8]
-                roi = cv2.resize(roi, (8 * (x_end - x_start), 8 * (y_end - y_start)))
-
-                cv2.imwrite(destination_directory + '\\cropped_npy_to_png.png', roi)
-                cv2.imshow("Cropped", roi)
-                cropped_img_png = Image.open(destination_directory + '\\cropped_npy_to_png.png')
-                numpy_crop = asarray(cropped_img_png)
-                np.save(destination_directory + '\\cropped.npy', numpy_crop)
-                np.save(test_data + '\\image_data_real_cropped.npy', image_data_real_cropped)
-                np.save(test_data + '\\image_data_imag_cropped.npy', image_data_imag_cropped)
-
-    h, w, c = image_png.shape
-    # resizing image
-    image = cv2.resize(image_png, (int(w / 8), int(h / 8)))
-    cv2.namedWindow("image")
-    cv2.setMouseCallback("image", mouse_crop)
-
-    while True:
-        i = image.copy()
-
-        if not cropping:
-            cv2.imshow("image", image)
-
-        elif cropping:
-            cv2.imshow("image", i)
-            cv2.rectangle(i, (x_start, y_start), (x_end, y_end), (255, 0, 0), 2)
-
-        key = cv2.waitKey(10)
-        if key == ord('q'):
-            cv2.destroyAllWindows()
-            return
-
-
-def crop_fixed(image_png, image_data_real, image_data_imag, destination_directory, test_data):
-    """ A crapping tool for despeckling only the selection of the user, made with OpenCV
-            Parameters
-            ----------
-            image_png: .png file
-            the image to be cropped in png format
-            image_data_real: nd.array
-            the real part of the image stored in an array
-            image_data_imag: nd.array
-            the imaginary part of the image stored in an array
-            destination_directory: string
-            the path for saving results in
-            test_data: string
-            the path for saving results in
-            cropping: bool
-            A boolean stating if the user wants to crop the image or not
-            Returns
-            ----------
-            None
-        """
-    # HERE I READ THE PNG FILE
-    oriImage = image_png.copy()
-    cropping = False
-    x_start, y_start, x_end, y_end = 0, 0, 0, 0
-
-    # CV2 CROPPING IN WINDOW
-    def mouse_crop_fixed(event, x, y, flags, param):
-        """ The callback function of crop() to deal with user's events
-        """
-        global x_start, y_start, x_end, y_end, cropping
-        cropping = False
-        if event == cv2.EVENT_LBUTTONDOWN:
-            x_start, y_start, x_end, y_end = x, y, x, y
-            cropping = True
-
-        # Mouse is Moving
-        elif event == cv2.EVENT_MOUSEMOVE:
-            cv2.rectangle(image, (x_start, y_start), (x_start + 32, y_start + 32), (255, 0, 0), 2)
-            x_end, y_end = x, y
-
-        # if the left mouse button was released
-        elif event == cv2.EVENT_LBUTTONUP:
-            # record the ending (x, y) coordinates
-            x_end, y_end = x_start + 32, y_start + 32
-            cropping = False  # cropping is finished
-
-            refPoint = [(x_start, y_start), (x_end, y_end)]
-
-            if len(refPoint) == 2:  # when two points were found
-                image_data_real_cropped = image_data_real[refPoint[0][1] * 8:refPoint[1][1] * 8,
-                                          refPoint[0][0] * 8:refPoint[1][0] * 8]
-                image_data_imag_cropped = image_data_imag[refPoint[0][1] * 8:refPoint[1][1] * 8,
-                                          refPoint[0][0] * 8:refPoint[1][0] * 8]
-                roi = oriImage[refPoint[0][1] * 8:refPoint[1][1] * 8, refPoint[0][0] * 8:refPoint[1][0] * 8]
-                roi = cv2.resize(roi, (256, 256))
-                cv2.imwrite(destination_directory + '\\cropped_npy_to_png.png', roi)
-                cv2.imshow("Cropped", roi)
-                cropped_img_png = Image.open(destination_directory + '\\cropped_npy_to_png.png')
-                numpy_crop = asarray(cropped_img_png)
-                np.save(destination_directory + '\\cropped.npy', numpy_crop)
-                np.save(test_data + '\\image_data_real_cropped.npy', image_data_real_cropped)
-                np.save(test_data + '\\image_data_imag_cropped.npy', image_data_imag_cropped)
-
-    h, w, c = image_png.shape
-    # resizing image
-    image = cv2.resize(image_png, (int(w / 8), int(h / 8)))
-    cv2.namedWindow("image")
-    cv2.setMouseCallback("image", mouse_crop_fixed)
-
-    while True:
-        i = image.copy()
-
-        if not cropping:
-            cv2.imshow("image", image)
-
-        elif cropping:
-            cv2.imshow("image", i)
-
-        key = cv2.waitKey(10)
-        if key == ord('q'):
-            cv2.destroyAllWindows()
-            return
-
-
-def get_info_image(image_path, destination_directory):
-    """ A function for retrieving informations on the CoSar stored image such as its png equivalent, its real and
-        imaginary part and the threshold to be applied later
-            Parameters
-            ----------
-            image_path: string
-            the path leading to image in CoSar format
-            destination_directory: string
-            the path for saving results in
-            Returns
-            ----------
-            None
-    """
-    image_data = cos2mat(image_path)
-
-    # GET THE TWO PARTS
-    image_data_real = image_data[:, :, 0]
-    image_data_imag = image_data[:, :, 1]
-
-    # GET NOISY FOR THRESHOLD
-    image = np.squeeze(np.sqrt(np.square(image_data_real) + np.square(image_data_imag)))
-    threshold = np.mean(image) + 3 * np.std(image)
-
-    # DISPLAY FULL PICTURE
-    filename = store_data_and_plot(image, threshold, destination_directory + '\\image_provided.npy')
-    print('full picture in png is saved')
-    image_png = cv2.imread(filename.replace('npy', 'png'))
-    print('full picture in png has a dimension of {size}'.format(size=image_png.shape))
-    return image_png, image_data, image_data_real, image_data_imag, threshold, filename
\ No newline at end of file
diff --git a/tests/sar2sar/test/__init__.py b/tests/sar2sar/test/__init__.py
deleted file mode 100644
index cc2be44..0000000
--- a/tests/sar2sar/test/__init__.py
+++ /dev/null
@@ -1,11 +0,0 @@
-"""Top-level package for merlin."""
-
-__author__ = """youcef kemiche"""
-__email__ = 'y.kemiche06@hotmail.com'
-__version__ = '0.1.0'
-
-from merlinsar.test.utils import *
-from merlinsar.test.load_cosar import *
-from merlinsar.test.model import *
-from merlinsar.test.model_test import *
-from merlinsar.test.spotlight import*
\ No newline at end of file
diff --git a/tests/sar2sar/test/load_cosar.py b/tests/sar2sar/test/load_cosar.py
deleted file mode 100644
index f3da7d9..0000000
--- a/tests/sar2sar/test/load_cosar.py
+++ /dev/null
@@ -1,46 +0,0 @@
-import struct
-import numpy as np
-
-def cos2mat(imgName):
-    print('Converting CoSAR to numpy array of size [ncolumns,nlines,2]')
-
-    try:
-        fin = open(imgName, 'rb');
-    except IOError:
-        legx = imgName + ': it is a not openable file'
-        print(legx)
-        print(u'failed to call cos2mat')
-        return 0, 0, 0, 0
-
-    ibib = struct.unpack(">i", fin.read(4))[0]
-    irsri = struct.unpack(">i", fin.read(4))[0]
-    irs = struct.unpack(">i", fin.read(4))[0]
-    ias = struct.unpack(">i", fin.read(4))[0]
-    ibi = struct.unpack(">i", fin.read(4))[0]
-    irtnb = struct.unpack(">i", fin.read(4))[0]
-    itnl = struct.unpack(">i", fin.read(4))[0]
-
-    nlig = struct.unpack(">i", fin.read(4))[0]
-    ncoltot = int(irtnb / 4)
-    ncol = ncoltot - 2
-    nlig = ias
-
-    print(u'Reading image in CoSAR format.  ncolumns=%d  nlines=%d' % (ncol, nlig))
-
-    firm = np.zeros(4 * ncoltot, dtype=np.byte())
-    imgcxs = np.empty([nlig, ncol], dtype=np.complex64())
-
-    fin.seek(0)
-    firm = fin.read(4 * ncoltot)
-    firm = fin.read(4 * ncoltot)
-    firm = fin.read(4 * ncoltot)
-    firm = fin.read(4 * ncoltot)
-    #
-    for iut in range(nlig):
-        firm = fin.read(4 * ncoltot)
-        imgligne = np.ndarray(2 * ncoltot, '>h', firm)
-        imgcxs[iut, :] = imgligne[4:2 * ncoltot:2] + 1j * imgligne[5:2 * ncoltot:2]
-
-    print('[:,:,0] contains the real part of the SLC image data')
-    print('[:,:,1] contains the imaginary part of the SLC image data')
-    return np.stack((np.real(imgcxs), np.imag(imgcxs)), axis=2)
\ No newline at end of file
diff --git a/tests/sar2sar/test/model.py b/tests/sar2sar/test/model.py
deleted file mode 100644
index 6cda078..0000000
--- a/tests/sar2sar/test/model.py
+++ /dev/null
@@ -1,154 +0,0 @@
-import time
-import numpy as np
-import os
-
-from merlinsar.test.utils import *
-from scipy import special
-import argparse
-
-
-# DEFINE PARAMETERS OF SPECKLE AND NORMALIZATION FACTOR
-M = 10.089038980848645
-m = -1.429329123112601
-L = 1
-c = (1 / 2) * (special.psi(L) - np.log(L))
-cn = c / (M - m)  # normalized (0,1) mean of log speckle
-
-import torch
-import numpy as np
-
-
-
-
-class Model(torch.nn.Module):
-
-    def __init__(self,height,width,device):
-        super().__init__()
-
-        self.device=device
-
-        self.height = height
-        self.width = width
-
-
-        self.pool = torch.nn.MaxPool2d(kernel_size=2, stride=2)
-        self.leaky = torch.nn.LeakyReLU(0.1)
-
-        self.enc0 = torch.nn.Conv2d(in_channels=1, out_channels=48, kernel_size=(3, 3), stride=(1, 1),
-                                    padding='same')
-        self.enc1 = torch.nn.Conv2d(in_channels=48, out_channels=48, kernel_size=(3, 3), stride=(1, 1),
-                                    padding='same')
-        self.enc2 = torch.nn.Conv2d(in_channels=48, out_channels=48, kernel_size=(3, 3), stride=(1, 1),
-                                    padding='same')
-        self.enc3 = torch.nn.Conv2d(in_channels=48, out_channels=48, kernel_size=(3, 3), stride=(1, 1),
-                                    padding='same')
-        self.enc4 = torch.nn.Conv2d(in_channels=48, out_channels=48, kernel_size=(3, 3), stride=(1, 1),
-                                    padding='same')
-        self.enc5 = torch.nn.Conv2d(in_channels=48, out_channels=48, kernel_size=(3, 3), stride=(1, 1),
-                                    padding='same')
-        self.enc6 = torch.nn.Conv2d(in_channels=48, out_channels=48, kernel_size=(3, 3), stride=(1, 1),
-                                    padding='same')
-
-        self.dec5 = torch.nn.Conv2d(in_channels=96, out_channels=96, kernel_size=(3, 3), stride=(1, 1),
-                                    padding='same')
-        self.dec5b = torch.nn.Conv2d(in_channels=96, out_channels=96, kernel_size=(3, 3), stride=(1, 1),
-                                     padding='same')
-        self.dec4 = torch.nn.Conv2d(in_channels=144, out_channels=96, kernel_size=(3, 3), stride=(1, 1),
-                                    padding='same')
-        self.dec4b = torch.nn.Conv2d(in_channels=96, out_channels=96, kernel_size=(3, 3), stride=(1, 1),
-                                     padding='same')
-        self.dec3 = torch.nn.Conv2d(in_channels=144, out_channels=96, kernel_size=(3, 3), stride=(1, 1),
-                                    padding='same')
-        self.dec3b = torch.nn.Conv2d(in_channels=96, out_channels=96, kernel_size=(3, 3), stride=(1, 1),
-                                     padding='same')
-        self.dec2 = torch.nn.Conv2d(in_channels=144, out_channels=96, kernel_size=(3, 3), stride=(1, 1),
-                                    padding='same')
-        self.dec2b = torch.nn.Conv2d(in_channels=96, out_channels=96, kernel_size=(3, 3), stride=(1, 1),
-                                     padding='same')
-        self.dec1a = torch.nn.Conv2d(in_channels=97, out_channels=64, kernel_size=(3, 3), stride=(1, 1),
-                                     padding='same')
-        self.dec1b = torch.nn.Conv2d(in_channels=64, out_channels=32, kernel_size=(3, 3), stride=(1, 1),
-                                     padding='same')
-        self.dec1 = torch.nn.Conv2d(in_channels=32, out_channels=1, kernel_size=(3, 3), stride=(1, 1),
-                                    padding='same')
-
-        self.upscale2d = torch.nn.UpsamplingNearest2d(scale_factor=2)
-
-
-    def forward(self,x):
-        """  Defines a class for an autoencoder algorithm for an object (image) x
-
-        An autoencoder is a specific type of feedforward neural networks where the
-        input is the same as the
-        output. It compresses the input into a lower-dimensional code and then 
-        reconstruct the output from this representattion. It is a dimensionality 
-        reduction algorithm
-
-        Parameters
-        ----------
-        x : np.array
-        a numpy array containing image 
-
-        Returns
-        ----------
-        x-n : np.array
-        a numpy array containing the denoised image i.e the image itself minus the noise
-
-        """
-        x=torch.reshape(x, [1, 1, self.height, self.width])
-        skips = [x]
-
-        n = x
-
-        # ENCODER
-        n = self.leaky(self.enc0(n))
-        n = self.leaky(self.enc1(n))
-        n = self.pool(n)
-        skips.append(n)
-
-        n = self.leaky(self.enc2(n))
-        n = self.pool(n)
-        skips.append(n)
-
-        n = self.leaky(self.enc3(n))
-        n = self.pool(n)
-        skips.append(n)
-
-        n = self.leaky(self.enc4(n))
-        n = self.pool(n)
-        skips.append(n)
-
-        n = self.leaky(self.enc5(n))
-        n = self.pool(n)
-        n = self.leaky(self.enc6(n))
-
-
-        # DECODER
-        n = self.upscale2d(n)
-        n = torch.cat((n, skips.pop()), dim=1)
-        n = self.leaky(self.dec5(n))
-        n = self.leaky(self.dec5b(n))
-
-        n = self.upscale2d(n)
-        n = torch.cat((n, skips.pop()), dim=1)
-        n = self.leaky(self.dec4(n))
-        n = self.leaky(self.dec4b(n))
-
-        n = self.upscale2d(n)
-        n = torch.cat((n, skips.pop()), dim=1)
-        n = self.leaky(self.dec3(n))
-        n = self.leaky(self.dec3b(n))
-
-        n = self.upscale2d(n)
-        n = torch.cat((n, skips.pop()), dim=1)
-        n = self.leaky(self.dec2(n))
-        n = self.leaky(self.dec2b(n))
-
-        n = self.upscale2d(n)
-        n = torch.cat((n, skips.pop()), dim=1)
-        n = self.leaky(self.dec1a(n))
-        n = self.leaky(self.dec1b(n))
-
-        n = self.dec1(n)
-
-        return x - n
diff --git a/tests/sar2sar/test/model_test.py b/tests/sar2sar/test/model_test.py
deleted file mode 100644
index 6324592..0000000
--- a/tests/sar2sar/test/model_test.py
+++ /dev/null
@@ -1,166 +0,0 @@
-from merlinsar.test.utils import *
-from merlinsar.test.model import *
-import torch
-import numpy as np
-from tqdm import tqdm 
-
-M = 10.089038980848645
-m = -1.429329123112601
-
-class Denoiser(object):
-    """ Description
-                ----------
-                A set of initial conditions, and transformations on the Y
-
-                Parameters
-                ----------
-                denoiser : an object
-
-                Returns
-                ----------
-    """
-
-    
-    def __init__(self, input_c_dim=1):
-
-        self.input_c_dim = input_c_dim
-
-
-    def load(self,model,weights_path):
-        """ Description
-                    ----------
-                    Restores a checkpoint located in a checkpoint repository
-
-                    Parameters
-                    ----------
-                    checkpoint_dir : a path leading to the checkpoint file
-
-                    Returns
-                    ----------
-                    True : Restoration is a success
-                    False: Restoration has failed
-        """
-        print("[*] Loading the model...")
-
-        model.load_state_dict(torch.load(weights_path))
-
-        return model
-
-     
-    def test(self,test_files, weights_path, save_dir,stride,patch_size,height,width):
-
-        """ Description
-                    ----------
-                    The function that does the job. Should be merged with main.py ?
-
-                    Parameters
-                    ----------
-                    test_files : a path leading to the checkpoint file
-                    ckpt_dir : repository containing the checkpoint (and weights)
-                    save_dir : repository to save sar images, real images and noisy images
-                    dataset_dir : the path to the test data
-                    stride : number of bytes from one row of pixels in memory to the next row of pixels in memory
-
-                    Returns
-                    ----------
-                    True : Restoration is a success
-                    False: Restoration has failed
-
-        """
-
-        """Test MERLIN"""
-
-        assert len(test_files) != 0, 'No testing data!'
-
-        loaded_model = Model(height,width,torch.device("cuda:0" if torch.cuda.is_available() else "cpu"))
-        loaded_model.load_state_dict(torch.load(weights_path, map_location=torch.device('cpu')))
-
-        # loaded_model = self.load(model,weights_path)
-        print()        
-        print(" [*] Load weights SUCCESS...")
-        print("[*] start testing...")
-        
-        for idx in range(len(test_files)):
-            real_image = load_sar_images(test_files[idx]).astype(np.float32)
-            i_real_part = (real_image[:, :, :, 0]).reshape(real_image.shape[0], real_image.shape[1],
-                                                           real_image.shape[2], 1)
-            i_imag_part = (real_image[:, :, :, 1]).reshape(real_image.shape[0], real_image.shape[1],
-                                                           real_image.shape[2], 1)
-
-
-    
-            # Pad the image
-            im_h = np.size(real_image, 1)
-            im_w = np.size(real_image, 2)
-
-            count_image = np.zeros(i_real_part.shape)
-            output_clean_image_1 = np.zeros(i_real_part.shape)
-            output_clean_image_2 = np.zeros(i_real_part.shape)
-
-            if im_h == patch_size:
-                x_range = list(np.array([0]))
-
-            else:
-                x_range = list(range(0, im_h - patch_size, stride))
-                if (x_range[-1] + patch_size) < im_h: x_range.extend(range(im_h - patch_size, im_h - patch_size + 1))
-
-            if im_w == patch_size:
-                y_range = list(np.array([0]))
-
-            else:
-                y_range = list(range(0, im_w - patch_size, stride))
-                if (y_range[-1] + patch_size) < im_w: y_range.extend(range(im_w - patch_size, im_w - patch_size + 1))
-
-            for x in tqdm(x_range):
-                for y in y_range:
-                  
-                    real_to_denoise, imag_to_denoise = symetrisation_patch_test(i_real_part[:, x:x + patch_size, y:y + patch_size, :],i_imag_part[:, x:x + patch_size, y:y + patch_size, :])
-
-                    real_to_denoise =torch.tensor(real_to_denoise)  
-                    imag_to_denoise=torch.tensor(imag_to_denoise)   
-
-                    real_to_denoise = real_to_denoise.type(torch.float32)
-                    imag_to_denoise = imag_to_denoise.type(torch.float32)
-
-                    real_to_denoise=(torch.log(torch.square(real_to_denoise)+1e-3)-2*m)/(2*(M-m))
-                    imag_to_denoise=(torch.log(torch.square(imag_to_denoise)+1e-3)-2*m)/(2*(M-m))
-
-            
-                    tmp_clean_image_real = loaded_model.forward(real_to_denoise).detach().numpy()                          
-                    tmp_clean_image_real=np.moveaxis(tmp_clean_image_real, 1, -1)
-
-                    output_clean_image_1[:, x:x + patch_size, y:y + patch_size, :] = output_clean_image_1[:, x:x + patch_size,
-                                                                                 y:y + patch_size,
-                                                                                 :] + tmp_clean_image_real
-
-
-
-                    tmp_clean_image_imag = loaded_model.forward(imag_to_denoise).detach().numpy()
-                    tmp_clean_image_imag=np.moveaxis(tmp_clean_image_imag, 1, -1)
-
-                    output_clean_image_2[:, x:x + patch_size, y:y + patch_size, :] = output_clean_image_2[:, x:x + patch_size,
-                                                                                 y:y + patch_size,
-                                                                                 :] + tmp_clean_image_imag
-                    count_image[:, x:x + patch_size, y:y + patch_size, :] = count_image[:, x:x + patch_size, y:y + patch_size,
-                                                                        :] + np.ones((1, patch_size, patch_size, 1))
-
-
-            output_clean_image_1 = output_clean_image_1 / count_image
-            output_clean_image_2 = output_clean_image_2 / count_image
-            output_clean_image = 0.5 * (np.square(denormalize_sar(output_clean_image_1)) + np.square(
-            denormalize_sar(output_clean_image_2))) # combine the two estimation
-
-
-            noisyimage = np.squeeze(np.sqrt(i_real_part ** 2 + i_imag_part ** 2))
-            outputimage = np.sqrt(np.squeeze(output_clean_image))
-
-            # imagename = test_files[idx].replace(dataset_dir, "") test_image_data
-            imagename = "test_image_data.npy"
-
-            print("Denoised image %s" % imagename)
-
-            save_sar_images(outputimage, noisyimage, imagename, save_dir)
-            save_real_imag_images( denormalize_sar(output_clean_image_1), denormalize_sar(output_clean_image_2),
-                                  imagename, save_dir)
-                                  
-            save_real_imag_images_noisy( np.squeeze(i_real_part), np.squeeze(i_imag_part), imagename, save_dir)
\ No newline at end of file
diff --git a/tests/sar2sar/test/results/denoised_imag_test_image_data.png b/tests/sar2sar/test/results/denoised_imag_test_image_data.png
deleted file mode 100644
index 8aa904a..0000000
Binary files a/tests/sar2sar/test/results/denoised_imag_test_image_data.png and /dev/null differ
diff --git a/tests/sar2sar/test/saved_model/model.pth b/tests/sar2sar/test/saved_model/model.pth
deleted file mode 100644
index 827285b..0000000
Binary files a/tests/sar2sar/test/saved_model/model.pth and /dev/null differ
diff --git a/tests/sar2sar/test/spotlight.py b/tests/sar2sar/test/spotlight.py
deleted file mode 100644
index 226f28f..0000000
--- a/tests/sar2sar/test/spotlight.py
+++ /dev/null
@@ -1,159 +0,0 @@
-import torch
-
-from merlinsar.test.model import *
-from merlinsar.test.utils import *
-from merlinsar.test.model_test import * 
-import os
-from glob import glob
-
-import numpy as np
-from merlinsar.test.load_cosar import cos2mat
-
-M = 10.089038980848645
-m = -1.429329123112601
-
-this_dir, this_filename = os.path.split(__file__)
-
-
-def despeckle(image_path,destination_directory,stride_size=64,
-                model_weights_path= os.path.join(this_dir, "saved_model", "model.pth"),patch_size=256,height=256,width=256):
-
-    """ Description
-            ----------
-            Runs a test instance by calling the test function defined in model.py on a few samples
-
-            Parameters
-            ----------
-            denoiser : an object
-
-            Returns
-            ----------
-
-    """
-
-    denoiser=Denoiser()
-
-    if not os.path.exists(destination_directory+'/processed_image'):
-        os.mkdir(destination_directory+'/processed_image')
-    
-    test_data=destination_directory+'/processed_image'
-
-    filelist = glob(os.path.join(test_data, "*"))
-    for f in filelist:
-        os.remove(f)
-
-    image_data = cos2mat(image_path) 
-
-    np.save(test_data+'/test_image_data.npy',image_data)
-
-    print(
-        "[*] Start testing on real data. Working directory: %s. Collecting data from %s and storing test results in %s" % (
-            os.getcwd(), destination_directory, destination_directory))
-            
-    test_files = glob((test_data + '/*.npy'))
-    print(test_files)
-
-    denoiser.test(test_files,model_weights_path, save_dir=destination_directory,
-                  stride=stride_size,patch_size=patch_size,height=height,width=width)
-        
-
-def despeckle_from_coordinates(image_path,coordinates_dict,destination_directory,stride_size=64,
-                model_weights_path= os.path.join(this_dir, "saved_model", "model.pth"),patch_size=256,height=256,width=256):
-    """ Description
-            ----------
-            Runs a test instance by calling the test function defined in model.py on a few samples
-
-            Parameters
-            ----------
-            denoiser : an object
-
-            Returns
-            ----------
-
-    """
-
-    x_start=coordinates_dict["x_start"]
-    x_end=coordinates_dict["x_end"]
-    y_start=coordinates_dict["y_start"]
-    y_end=coordinates_dict["y_end"]
-
-    denoiser=Denoiser()
-
-    if not os.path.exists(destination_directory+'/processed_image'):
-        os.mkdir(destination_directory+'/processed_image')
-    
-    test_data=destination_directory+'/processed_image'
- 
-    filelist = glob(os.path.join(test_data, "*"))
-    for f in filelist:
-        os.remove(f)
-
-    image_data = cos2mat(image_path) 
-
-    np.save(test_data+'/test_image_data.npy',image_data[x_start:x_end,y_start:y_end,:])
-
-    print(
-        "[*] Start testing on real data. Working directory: %s. Collecting data from %s and storing test results in %s" % (
-            os.getcwd(), destination_directory, destination_directory))
-            
-    test_files = glob((test_data + '/*.npy'))
-    print(test_files)
-    denoiser.test(test_files,model_weights_path, save_dir=destination_directory,
-                  stride=stride_size,patch_size=patch_size,height=height,width=width)
-
-
-
-def despeckle_from_crop(image_path,destination_directory,stride_size=64,
-                model_weights_path= os.path.join(this_dir, "saved_model", "model.pth"),patch_size=256,height=256,width=256):
-    """ Description
-            ----------
-            Runs a test instance by calling the test function defined in model.py on a few samples
-
-            Parameters
-            ----------
-            denoiser : an object
-
-            Returns
-            ----------
-
-    """
-
-    denoiser=Denoiser()
-
-    if not os.path.exists(destination_directory+'\\processed_image'):
-        os.mkdir(destination_directory+'\\processed_image')
-
-    test_data=destination_directory+'\\processed_image'
-
-    filelist = glob(os.path.join(test_data, "*"))
-    for f in filelist:
-        os.remove(f)
-
-
-    # FROM IMAGE PATH RETRIEVE PNG, NPY, REAL , IMAG, THRESHOLD, FILENAME
-    image_png , image_data, image_data_real , image_data_imag, threshold, filename = get_info_image(image_path,destination_directory)
-
-    # CROPPING OUR PNG AND REFLECT THE CROP ON REAL AND IMAG
-    cropping = False
-    crop(image_png,image_data_real, image_data_imag, destination_directory,test_data,cropping)
-
-
-    image_data_real_cropped = np.load(test_data+'\\image_data_real_cropped.npy')
-    store_data_and_plot(image_data_real_cropped, threshold, test_data+'\\image_data_real_cropped.npy')
-    image_data_imag_cropped =np.load(test_data+'\\image_data_imag_cropped.npy')
-    store_data_and_plot(image_data_imag_cropped, threshold, test_data+'\\image_data_imag_cropped.npy')
-
-    image_data_real_cropped= image_data_real_cropped.reshape(image_data_real_cropped.shape[0],image_data_real_cropped.shape[1],1)
-    image_data_imag_cropped = image_data_imag_cropped.reshape(image_data_imag_cropped.shape[0],
-                                                              image_data_imag_cropped.shape[1], 1)
-
-    np.save(test_data + '/test_image_data_cropped.npy', np.concatenate((image_data_real_cropped, image_data_imag_cropped), axis=2))
-
-    print(
-        "[*] Start testing on real data. Working directory: %s. Collecting data from %s and storing test results in %s" % (
-            os.getcwd(), destination_directory, destination_directory))
-
-    test_files = glob((test_data +'/test_image_data_cropped.npy'))
-    print(test_files)
-    denoiser.test(test_files,model_weights_path, save_dir=destination_directory,
-                  stride=stride_size,patch_size=patch_size,height=height,width=width)
diff --git a/tests/sar2sar/test/utils.py b/tests/sar2sar/test/utils.py
deleted file mode 100644
index 8e16f73..0000000
--- a/tests/sar2sar/test/utils.py
+++ /dev/null
@@ -1,325 +0,0 @@
-import numpy as np
-from PIL import Image
-from scipy import special
-from scipy import signal
-from merlinsar.test.load_cosar import cos2mat
-import cv2
-from PIL import Image
-import numpy as np
-from numpy import asarray
-
-
-# DEFINE PARAMETERS OF SPECKLE AND NORMALIZATION FACTOR
-M = 10.089038980848645
-m = -1.429329123112601
-L = 1
-c = (1 / 2) * (special.psi(L) - np.log(L))
-cn = c / (M - m)  # normalized (0,1) mean of log speckle
-
-
-def normalize_sar(im):
-    """ Description
-            ----------
-            Normalization of a numpy-stored image
-
-            Parameters
-            ----------
-            im : an image object
-
-            Returns
-            ----------
-
-        """
-    if not isinstance(im, np.ndarray):
-        raise TypeError('Please provide a .npy argument')
-    return ((np.log(im + np.spacing(1)) - m) * 255 / (M - m)).astype('float32')
-
-
-def denormalize_sar(im):
-    """ Description
-            ----------
-            Denormalization of a numpy image
-
-            Parameters
-            ----------
-            im : an image object
-
-            Returns
-            ----------
-
-        """
-    if not isinstance(im, np.ndarray):
-        raise TypeError('Please provide a .npy argument')
-    return np.exp((M - m) * (np.squeeze(im)).astype('float32') + m)
-
-
-def symetrisation_patch_test(real_part, imag_part):
-    if not isinstance(real_part, np.ndarray):
-        raise TypeError('Please provide a .npy argument')
-    if not isinstance(imag_part, np.ndarray):
-        raise TypeError('Please provide a .npy argument')
-    S = np.fft.fftshift(np.fft.fft2(real_part[0, :, :, 0] + 1j * imag_part[0, :, :, 0]))
-    p = np.zeros((S.shape[0]))  # azimut (ncol)
-    for i in range(S.shape[0]):
-        p[i] = np.mean(np.abs(S[i, :]))
-    sp = p[::-1]
-    c = np.real(np.fft.ifft(np.fft.fft(p) * np.conjugate(np.fft.fft(sp))))
-    d1 = np.unravel_index(c.argmax(), p.shape[0])
-    d1 = d1[0]
-    shift_az_1 = int(round(-(d1 - 1) / 2)) % p.shape[0] + int(p.shape[0] / 2)
-    p2_1 = np.roll(p, shift_az_1)
-    shift_az_2 = int(round(-(d1 - 1 - p.shape[0]) / 2)) % p.shape[0] + int(p.shape[0] / 2)
-    p2_2 = np.roll(p, shift_az_2)
-    window = signal.gaussian(p.shape[0], std=0.2 * p.shape[0])
-    test_1 = np.sum(window * p2_1)
-    test_2 = np.sum(window * p2_2)
-    # make sure the spectrum is symetrized and zeo-Doppler centered
-    if test_1 >= test_2:
-        p2 = p2_1
-        shift_az = shift_az_1 / p.shape[0]
-    else:
-        p2 = p2_2
-        shift_az = shift_az_2 / p.shape[0]
-    S2 = np.roll(S, int(shift_az * p.shape[0]), axis=0)
-
-    q = np.zeros((S.shape[1]))  # range (nlin)
-    for j in range(S.shape[1]):
-        q[j] = np.mean(np.abs(S[:, j]))
-    sq = q[::-1]
-    # correlation
-    cq = np.real(np.fft.ifft(np.fft.fft(q) * np.conjugate(np.fft.fft(sq))))
-    d2 = np.unravel_index(cq.argmax(), q.shape[0])
-    d2 = d2[0]
-    shift_range_1 = int(round(-(d2 - 1) / 2)) % q.shape[0] + int(q.shape[0] / 2)
-    q2_1 = np.roll(q, shift_range_1)
-    shift_range_2 = int(round(-(d2 - 1 - q.shape[0]) / 2)) % q.shape[0] + int(q.shape[0] / 2)
-    q2_2 = np.roll(q, shift_range_2)
-    window_r = signal.gaussian(q.shape[0], std=0.2 * q.shape[0])
-    test_1 = np.sum(window_r * q2_1)
-    test_2 = np.sum(window_r * q2_2)
-    if test_1 >= test_2:
-        q2 = q2_1
-        shift_range = shift_range_1 / q.shape[0]
-    else:
-        q2 = q2_2
-        shift_range = shift_range_2 / q.shape[0]
-
-    Sf = np.roll(S2, int(shift_range * q.shape[0]), axis=1)
-    ima2 = np.fft.ifft2(np.fft.ifftshift(Sf))
-    ima2 = ima2.reshape(1, np.size(ima2, 0), np.size(ima2, 1), 1)
-    return np.real(ima2), np.imag(ima2)
-
-
-def load_sar_images(filelist):
-    """ Description
-            ----------
-            Loads files , resize them and append them into a list called data
-
-            Parameters
-            ----------
-            filelist : a path to a folder containing the images
-
-            Returns
-            ----------
-            A list of images
-
-        """
-    if not isinstance(filelist, list):
-        im = np.load(filelist)
-        return np.array(im).reshape(1, np.size(im, 0), np.size(im, 1), 2)
-    data = []
-    for file in filelist:
-        im = np.load(file)
-        data.append(np.array(im).reshape(1, np.size(im, 0), np.size(im, 1), 2))
-    return data
-
-
-def store_data_and_plot(im, threshold, filename):
-    """ Description
-            ----------
-            Creates an image memory from an object exporting the array interface and returns a
-            converted copy of this image into greyscale mode ("L")
-
-            However, there is not plotting functions' call ?
-
-            Parameters
-            ----------
-            im : the image to store
-            threshold: clip a maximum value in the image array i.e values are to be between 0 and threshold
-            filename: the path to store the result array image in .png
-
-            Returns
-            ----------
-            None
-
-        """
-    if not isinstance(im, np.ndarray):
-        raise TypeError('Please provide a .npy argument')
-    im = np.clip(im, 0, threshold)
-
-    im = im / threshold * 255
-    im = Image.fromarray(im.astype('float64')).convert('L')
-    im.save(filename.replace('npy', 'png'))
-    return filename
-
-def save_sar_images(denoised, noisy, imagename, save_dir, groundtruth=None):
-    choices = {'marais1': 190.92, 'marais2': 168.49, 'saclay': 470.92, 'lely': 235.90, 'ramb': 167.22,
-               'risoul': 306.94, 'limagne': 178.43, 'saintgervais': 560, 'Serreponcon': 450.0,
-               'Sendai': 600.0, 'Paris': 1291.0, 'Berlin': 1036.0, 'Bergen': 553.71,
-               'SDP_Lambesc': 349.53, 'Grand_Canyon': 287.0, 'domancy': 560, 'Brazil': 103.0}
-    threshold = None
-    for x in choices:
-        if x in imagename:
-            threshold = choices.get(x)
-    if threshold is None: threshold = np.mean(noisy) + 3 * np.std(noisy)
-
-    ####
-    imagename = imagename.split('\\')[-1]
-    ####
-
-    if groundtruth:
-        groundtruthfilename = save_dir + "/groundtruth_" + imagename
-        np.save(groundtruthfilename, groundtruth)
-        store_data_and_plot(groundtruth, threshold, groundtruthfilename)
-
-    denoisedfilename = save_dir + "/denoised_" + imagename
-    np.save(denoisedfilename, denoised)
-    store_data_and_plot(denoised, threshold, denoisedfilename)
-
-    noisyfilename = save_dir + "/noisy_" + imagename
-    np.save(noisyfilename, noisy)
-    store_data_and_plot(noisy, threshold, noisyfilename)
-
-
-def save_real_imag_images(real_part, imag_part, imagename, save_dir):
-    choices = {'marais1': 190.92, 'marais2': 168.49, 'saclay': 470.92, 'lely': 235.90, 'ramb': 167.22,
-               'risoul': 306.94, 'limagne': 178.43, 'saintgervais': 560, 'Serreponcon': 450.0,
-               'Sendai': 600.0, 'Paris': 1291.0, 'Berlin': 1036.0, 'Bergen': 553.71,
-               'SDP_Lambesc': 349.53, 'Grand_Canyon': 287.0, 'Brazil': 103.0}
-    threshold = None
-    for x in choices:
-        if x in imagename:
-            threshold = choices.get(x)
-    if threshold is None: threshold = np.mean(imag_part) + 3 * np.std(imag_part)
-
-    ####
-    imagename = imagename.split('\\')[-1]
-    ####
-
-    realfilename = save_dir + "/denoised_real_" + imagename
-    np.save(realfilename, real_part)
-    store_data_and_plot(real_part, threshold, realfilename)
-
-    imagfilename = save_dir + "/denoised_imag_" + imagename
-    np.save(imagfilename, imag_part)
-    store_data_and_plot(imag_part, threshold, imagfilename)
-
-
-def save_real_imag_images_noisy(real_part, imag_part, imagename, save_dir):
-    choices = {'marais1': 190.92, 'marais2': 168.49, 'saclay': 470.92, 'lely': 235.90, 'ramb': 167.22,
-               'risoul': 306.94, 'limagne': 178.43, 'saintgervais': 560, 'Serreponcon': 450.0,
-               'Sendai': 600.0, 'Paris': 1291.0, 'Berlin': 1036.0, 'Bergen': 553.71,
-               'SDP_Lambesc': 349.53, 'Grand_Canyon': 287.0, 'Brazil': 103.0}
-    threshold = None
-    for x in choices:
-        if x in imagename:
-            threshold = choices.get(x)
-    if threshold is None: threshold = np.mean(np.abs(imag_part)) + 3 * np.std(np.abs(imag_part))
-
-    ####
-    imagename = imagename.split('\\')[-1]
-    ####
-
-    realfilename = save_dir + "/noisy_real_" + imagename
-    np.save(realfilename, real_part)
-    store_data_and_plot(np.sqrt(2) * np.abs(real_part), threshold, realfilename)
-
-    imagfilename = save_dir + "/noisy_imag_" + imagename
-    np.save(imagfilename, imag_part)
-    store_data_and_plot(np.sqrt(2) * np.abs(imag_part), threshold, imagfilename)
-
-def cal_psnr(Shat, S):
-    # takes amplitudes in input
-    # Shat: a SAR amplitude image
-    # S:    a reference SAR image
-    P = np.quantile(S, 0.99)
-    res = 10 * np.log10((P ** 2) / np.mean(np.abs(Shat - S) ** 2))
-    return res
-
-def crop(image_png,image_data_real, image_data_imag,destination_directory,test_data, cropping):
-    # HERE I READ THE PNG FILE
-    oriImage = image_png.copy()
-    cropping = False
-    x_start, y_start, x_end, y_end = 0, 0, 0, 0
-
-    # CV2 CROPPING IN WINDOW
-    def mouse_crop(event, x, y, flags, param):
-        global x_start, y_start, x_end, y_end, cropping
-        cropping = False
-        if event == cv2.EVENT_LBUTTONDOWN:
-            x_start, y_start, x_end, y_end = x, y, x, y
-            cropping = True
-
-        # Mouse is Moving
-        elif event == cv2.EVENT_MOUSEMOVE:
-            cropping = True
-            if cropping:
-                cv2.rectangle(image, (x_start, y_start), (x_start + 32, y_start + 32), (255, 0, 0), 2)
-                x_end, y_end = x, y
-
-        # if the left mouse button was released
-        elif event == cv2.EVENT_LBUTTONUP:
-            # record the ending (x, y) coordinates
-            x_end, y_end = x_start + 32, y_start + 32
-            cropping = False  # cropping is finished
-
-            refPoint = [(x_start, y_start), (x_end, y_end)]
-
-            if len(refPoint) == 2:  # when two points were found
-                image_data_real_cropped = image_data_real[refPoint[0][1] * 8:refPoint[1][1] * 8, refPoint[0][0] * 8:refPoint[1][0] * 8]
-                image_data_imag_cropped = image_data_imag[refPoint[0][1] * 8:refPoint[1][1] * 8, refPoint[0][0] * 8:refPoint[1][0] * 8]
-                roi = oriImage[refPoint[0][1] * 8:refPoint[1][1] * 8, refPoint[0][0] * 8:refPoint[1][0] * 8]
-                roi = cv2.resize(roi, (256, 256))
-                cv2.imwrite(destination_directory+'\\cropped_npy_to_png.png', roi)
-                cv2.imshow("Cropped", roi)
-                cropped_img_png = Image.open(destination_directory+'\\cropped_npy_to_png.png')
-                numpy_crop = asarray(cropped_img_png)
-                np.save(destination_directory+'\\cropped.npy', numpy_crop)
-                np.save(test_data+'\\image_data_real_cropped.npy', image_data_real_cropped)
-                np.save(test_data + '\\image_data_imag_cropped.npy', image_data_imag_cropped)
-
-    h, w , c = image_png.shape
-    image = cv2.resize(image_png, (int(w / 8), int(h / 8)))
-    cv2.namedWindow("image")
-    cv2.setMouseCallback("image", mouse_crop)
-
-    while True:
-        i = image.copy()
-
-        if not cropping:
-            cv2.imshow("image", image)
-
-        elif cropping:
-            cv2.imshow("image", i)
-
-        key = cv2.waitKey(10)
-        if key == ord('q'):
-            return
-
-def get_info_image(image_path,destination_directory):
-    image_data = cos2mat(image_path)
-
-    # GET THE TWO PARTS
-    image_data_real = image_data[:, :, 0]
-    image_data_imag = image_data[:, :, 1]
-
-    # GET NOISY FOR THRESHOLD
-    image = np.squeeze(np.sqrt(np.square(image_data_real) + np.square(image_data_imag)))
-    threshold = np.mean(image) + 3 * np.std(image)
-
-    # DISPLAY FULL PICTURE
-    filename = store_data_and_plot(image, threshold, destination_directory + '\\image_provided.npy')
-    print('full picture in png is saved')
-    image_png = cv2.imread(filename.replace('npy', 'png'))
-    print('full picture in png has a dimension of {size}'.format(size=image_png.shape))
-    return image_png , image_data, image_data_real , image_data_imag, threshold, filename
diff --git a/tests/sar2sar/train/.directory b/tests/sar2sar/train/.directory
deleted file mode 100644
index 3fa4480..0000000
--- a/tests/sar2sar/train/.directory
+++ /dev/null
@@ -1,4 +0,0 @@
-[Dolphin]
-Timestamp=2022,4,5,17,1,21
-Version=4
-ViewMode=1
diff --git a/tests/sar2sar/train/GenerateDataset.py b/tests/sar2sar/train/GenerateDataset.py
deleted file mode 100644
index c605613..0000000
--- a/tests/sar2sar/train/GenerateDataset.py
+++ /dev/null
@@ -1,165 +0,0 @@
-import glob
-import random
-import os
-import numpy as np
-from scipy import signal
-from scipy import special
-
-
-'''
-Generate patches for the images in the folder dataset/data/Train
-The code scans among the training images and then for data_aug_times
-'''
-
-class GenerateDataset():
-    def data_augmentation(self, image, mode):
-        if mode == 0:
-            # original
-            return image
-        elif mode == 1:
-            # flip up and down
-            return np.flipud(image)
-        elif mode == 2:
-            # rotate counterwise 90 degree
-            return np.rot90(image)
-        elif mode == 3:
-            # rotate 90 degree and flip up and down
-            image = np.rot90(image)
-            return np.flipud(image)
-        elif mode == 4:
-            # rotate 180 degree
-            return np.rot90(image, k=2)
-        elif mode == 5:
-            # rotate 180 degree and flip
-            image = np.rot90(image, k=2)
-            return np.flipud(image)
-        elif mode == 6:
-            # rotate 270 degree
-            return np.rot90(image, k=3)
-        elif mode == 7:
-            # rotate 270 degree and flip
-            image = np.rot90(image, k=3)
-            return np.flipud(image)
-
-    def symetrisation_patch_gen(self, ima):
-        S = np.fft.fftshift(np.fft.fft2(ima[:,:,0]+1j*ima[:,:,1]))
-        p = np.zeros((S.shape[0])) # azimut (ncol)
-        for i in range(S.shape[0]):
-            p[i] = np.mean(np.abs(S[i,:]))
-        sp = p[::-1]
-        c = np.real(np.fft.ifft(np.fft.fft(p)*np.conjugate(np.fft.fft(sp))))
-        d1 = np.unravel_index(c.argmax(),p.shape[0])
-        d1 = d1[0]
-        shift_az_1 = int(round(-(d1-1)/2))%p.shape[0]+int(p.shape[0]/2)
-        p2_1 = np.roll(p,shift_az_1)
-        shift_az_2 = int(round(-(d1-1-p.shape[0])/2))%p.shape[0]+int(p.shape[0]/2)
-        p2_2 = np.roll(p,shift_az_2)
-        window = signal.gaussian(p.shape[0], std=0.2*p.shape[0])
-        test_1 = np.sum(window*p2_1)
-        test_2 = np.sum(window*p2_2)
-        # make sure the spectrum is symetrized and zeo-Doppler centered
-        if test_1>=test_2:
-            p2 = p2_1
-            shift_az = shift_az_1/p.shape[0]
-        else:
-            p2 = p2_2
-            shift_az = shift_az_2/p.shape[0]
-        S2 = np.roll(S,int(shift_az*p.shape[0]),axis=0)
-
-
-
-
-        q = np.zeros((S.shape[1])) # range (nlin)
-        for j in range(S.shape[1]):
-            q[j] = np.mean(np.abs(S[:,j]))
-        sq = q[::-1]
-        #correlation
-        cq = np.real(np.fft.ifft(np.fft.fft(q)*np.conjugate(np.fft.fft(sq))))
-        d2 = np.unravel_index(cq.argmax(),q.shape[0])
-        d2=d2[0]
-        shift_range_1 = int(round(-(d2-1)/2))%q.shape[0]+int(q.shape[0]/2)
-        q2_1 = np.roll(q,shift_range_1)
-        shift_range_2 = int(round(-(d2-1-q.shape[0])/2))%q.shape[0]+int(q.shape[0]/2)
-        q2_2 = np.roll(q,shift_range_2)
-        window_r = signal.gaussian(q.shape[0], std=0.2*q.shape[0])
-        test_1 = np.sum(window_r*q2_1)
-        test_2 = np.sum(window_r*q2_2)
-        if test_1>=test_2:
-            q2 = q2_1
-            shift_range = shift_range_1/q.shape[0]
-        else:
-            q2 = q2_2
-            shift_range = shift_range_2/q.shape[0]
-
-
-        Sf = np.roll(S2,int(shift_range*q.shape[0]),axis=1)
-        ima2 = np.fft.ifft2(np.fft.ifftshift(Sf))
-        return np.stack((np.real(ima2),np.imag(ima2)),axis=2)
-
-
-
-
-
-    def generate_patches(self,src_dir="./dataset/data/Train",pat_size=256,step=0,stride=64,bat_size=4,data_aug_times=1,n_channels=2):
-        count = 0
-        filepaths = glob.glob(src_dir + '/*.npy')
-        print("number of training data %d" % len(filepaths))
-
-        # calculate the number of patches
-        for i in range(len(filepaths)):
-            img = np.load(filepaths[i])
-
-            im_h = np.size(img, 0)
-            im_w = np.size(img, 1)
-            for x in range(0+step, (im_h - pat_size), stride):
-                for y in range(0+step, (im_w - pat_size), stride):
-                    count += 1
-        origin_patch_num = count * data_aug_times
-
-        if origin_patch_num % bat_size != 0:
-            numPatches = (origin_patch_num / bat_size + 1) * bat_size
-        else:
-            numPatches = origin_patch_num
-        print("total patches = %d , batch size = %d, total batches = %d" % \
-              (numPatches, bat_size, numPatches / bat_size))
-
-        # data matrix 4-D
-        numPatches=int(numPatches)
-        inputs = np.zeros((numPatches, pat_size, pat_size, n_channels), dtype="float32")
-
-
-        count = 0
-        # generate patches
-        for i in range(len(filepaths)): #scan through images
-            img = np.load(filepaths[i])
-            img_s = img
-
-
-            # If data_aug_times = 8 then perform them all, otherwise pick one at random or do nothing
-            for j in range(data_aug_times):
-                im_h = np.size(img, 0)
-                im_w = np.size(img, 1)
-                if data_aug_times == 8:
-                    for x in range(0 + step, im_h - pat_size, stride):
-                        for y in range(0 + step, im_w - pat_size, stride):
-                            inputs[count, :, :, :] = self.data_augmentation(img_s[x:x + pat_size, y:y + pat_size, :], \
-                                  j)
-                            count += 1
-                else:
-                    for x in range(0 + step, im_h - pat_size, stride):
-                        for y in range(0 + step, im_w - pat_size, stride):
-                            # to pick one at random, uncomment this line and comment the one below
-                            """inputs[count, :, :, :] = self.data_augmentation(img_s[x:x + pat_size, y:y + pat_size, :], \
-                                                                          random.randint(0, 7))"""
-
-                            inputs[count, :, :, :] = self.data_augmentation(self.symetrisation_patch_gen(img_s[x:x + pat_size, y:y + pat_size, :]),0)
-
-                            count += 1
-
-
-        # pad the batch
-        if count < numPatches:
-            to_pad = numPatches - count
-            inputs[-to_pad:, :, :, :] = inputs[:to_pad, :, :, :]
-
-        return inputs
diff --git a/tests/sar2sar/train/__init__.py b/tests/sar2sar/train/__init__.py
deleted file mode 100644
index 7f787ab..0000000
--- a/tests/sar2sar/train/__init__.py
+++ /dev/null
@@ -1,12 +0,0 @@
-"""Top-level package for merlin."""
-
-__author__ = """youcef kemiche"""
-__email__ = 'y.kemiche06@hotmail.com'
-__version__ = '0.1.0'
-
-
-from merlinsar.train.utils import *
-from merlinsar.train.Dataset import *
-from merlinsar.train.GenerateDataset import *
-from merlinsar.train.model import *
-from merlinsar.train.merlin import*
\ No newline at end of file
diff --git a/tests/sar2sar/train/merlin.py b/tests/sar2sar/train/merlin.py
deleted file mode 100644
index bd3025b..0000000
--- a/tests/sar2sar/train/merlin.py
+++ /dev/null
@@ -1,150 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-import argparse
-from glob import glob
-import os
-
-import numpy as np
-import torch
-
-from merlinsar.train.Dataset import *
-from merlinsar.train.model import *
-from merlinsar.train.utils import *
-
-
-def evaluate(model, loader):
-    outputs = [model.validation_step(batch) for batch in loader]
-    outputs = torch.tensor(outputs).T
-    loss, accuracy = torch.mean(outputs, dim=1)
-    return {"loss": loss.item(), "accuracy": accuracy.item()}
-
-
-def save_model(model,destination_folder):
-    """
-      save the ".pth" model in destination_folder
-    """
-    # Check whether the specified path exists or not
-    isExist = os.path.exists(destination_folder)
-
-    if not isExist:
-
-      # Create a new directory because it does not exist
-      os.makedirs(destination_folder)
-      print("The new directory is created!")
-
-      torch.save(model.state_dict(),destination_folder+"/model.pth")
-
-    else:
-      torch.save(model.state_dict(),destination_folder+"/model.pth")
-
-
-def fit(model,train_loader,val_loader,epochs,lr_list,eval_files,eval_set,clip_by_norm,sample_dir):
-  """ Fit the model according to the given evaluation data and parameters.
-
-  Parameters
-  ----------
-  model : model as defined in main
-  train_loader : Pytorch's DataLoader of training data
-  val_loader : Pytorch's DataLoader of validation data
-  lr_list : list of learning rates
-  eval_files : .npy files used for evaluation in training
-  eval_set : directory of dataset used for evaluation in training
-
-  Returns
-  ----------
-  self : object
-    Fitted estimator.
-
-  """
-
-  train_losses = []
-  history={}
-  epoch_num=0
-  for epoch in range(epochs):
-      epoch_num=epoch_num+1
-      print("\nEpoch", epoch+1)
-      print("***************** \n")
-      optimizer=torch.optim.Adam(model.parameters(), lr=lr_list[epoch])
-
-      #Train
-      for i, batch in enumerate(train_loader, 0):
-
-            optimizer.zero_grad()
-            loss = model.training_step(batch,i)
-            train_losses.append(loss)
-
-            loss.backward()
-
-            if(clip_by_norm==True):
-                  
-              torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=5.0)
-
-            optimizer.step()
-
-            #running_loss += loss.item()     # extract the loss value
-            print('[%d, %5d] loss: %.3f' % (epoch + 1, i + 1, loss))
-            # zero the loss
-
-          # #Validate
-      with torch.no_grad():
-        image_num=0
-        for batch in val_loader:
-            val_loss=model.validation_step(batch,image_num,epoch_num,eval_files,eval_set,sample_dir)
-            image_num=image_num+1
-
-  history["train_loss"]=train_losses
-
-  return history
-
-
-def create_model(height=256,width=256,batch_size=12,val_batch_size=1,device=torch.device("cuda:0" if torch.cuda.is_available() else "cpu"),from_pretrained=False):
-  """ Runs the denoiser algorithm for the training and evaluation dataset
-
-
-  """
-  this_dir, this_filename = os.path.split(__file__)
-  weights_path = os.path.join(this_dir, "saved_model", "model.pth")
-
-  model = Model(height,width,batch_size,val_batch_size,device)
-  model.to(device)
-  
-  if from_pretrained == True:
-        
-    model.load_state_dict(torch.load(weights_path, map_location=torch.device('cpu')))
-
-  return model
-
-def fit_model(model,lr_list,nb_epoch,training_set_directory,validation_set_directory,sample_directory,save_directory,patch_size=256,batch_size=12,val_batch_size=1,stride_size=128,n_data_augmentation=1,seed=2,clip_by_norm=True):
-  """ Runs the denoiser algorithm for the training and evaluation dataset
-
-  Parameters
-  ----------
-  model : model as defined in main
-  lr_list : list of learning rates
-
-  Returns
-  ----------
-  history : list of both training and validation loss
-
-  """
-  torch.manual_seed(seed)
-
-
-  # Prepare train DataLoader
-  train_data = load_train_data(training_set_directory,patch_size,batch_size,stride_size,n_data_augmentation) # range [0; 1]
-  train_dataset = Dataset(train_data)
-  train_loader = torch.utils.data.DataLoader(train_dataset,batch_size=batch_size,shuffle=True,drop_last=True)
-
-  # Prepare Validation DataLoader
-  eval_dataset = ValDataset(validation_set_directory)
-  eval_loader = torch.utils.data.DataLoader(eval_dataset,batch_size=val_batch_size,shuffle=False,drop_last=True)
-
-  eval_files = glob(validation_set_directory+'/*.npy')
-  # Train the model
-  history =fit(model,train_loader,eval_loader,nb_epoch,lr_list,eval_files,validation_set_directory,clip_by_norm,sample_directory)
-
-  # Save the model
-  save_model(model,save_directory)
-  print("\n model saved at :",save_directory)
-   
-  return history
diff --git a/tests/sar2sar/train/model.py b/tests/sar2sar/train/model.py
deleted file mode 100644
index 5f4c2bd..0000000
--- a/tests/sar2sar/train/model.py
+++ /dev/null
@@ -1,306 +0,0 @@
-import numpy as np
-
-from merlinsar.train.utils import *
-from scipy import special
-
-
-# DEFINE PARAMETERS OF SPECKLE AND NORMALIZATION FACTOR
-M = 10.089038980848645
-m = -1.429329123112601
-L = 1
-c = (1 / 2) * (special.psi(L) - np.log(L))
-cn = c / (M - m)  # normalized (0,1) mean of log speckle
-
-import torch
-import numpy as np
-
-
-# Nearest-neighbor up-scaling layer.
-'''def upscale2d(x, factor=2):
-    """ Description
-            ----------
-            Run a nearest-neighbor up-scaling layer for a given factor on an object (image) x
-
-            This is a common quality-enhancing algorithm  that doubles the dimensions of the input.
-
-            Every pixel in low-res is thereafter transformed into 4 pixels.
-
-            Parameters
-            ----------
-            x : an image object
-
-            factor : figure by how many times ou want to multiply the input's initial dimensions
-
-            Returns
-            ----------
-            x if factor is 1
-            or
-            a reshaped x if factor is different from 1
-        """
-    assert isinstance(factor, int) and factor >= 1
-    if factor == 1:
-        return x
-    # with tf.compat.v1.variable_scope('Upscale2D'):
-    s = x.shape
-    x = torch.reshape(x, [-1, s[1], 1, s[2], 1, s[3]])
-    x = torch.tile(x, [1, 1, factor, 1, factor, 1])
-    x = torch.reshape(x, [-1, s[1] , s[2] * factor, s[3]* factor])
-    
-    
-    s = x.shape
-    x = torch.reshape(x, [-1, s[1], s[2], 1, s[3], 1])
-    x = torch.tile(x, [1, 1, 1, factor, 1, factor])
-    x = torch.reshape(x, [-1, s[1], s[2] * factor, s[3] * factor])
-
-    return x'''
-
-
-
-class Model(torch.nn.Module):
-
-    def __init__(self,height,width,batch_size,eval_batch_size,device):
-        super().__init__()
-
-        self.batch_size=batch_size
-        self.eval_batch_size=eval_batch_size
-        self.device=device
-
-        self.height = height
-        self.width = width
-
-        self.pool = torch.nn.MaxPool2d(kernel_size=2, stride=2)
-        self.leaky = torch.nn.LeakyReLU(0.1)
-
-        self.enc0 = torch.nn.Conv2d(in_channels=1, out_channels=48, kernel_size=(3, 3), stride=(1, 1),
-                                    padding='same')
-        self.enc1 = torch.nn.Conv2d(in_channels=48, out_channels=48, kernel_size=(3, 3), stride=(1, 1),
-                                    padding='same')
-        self.enc2 = torch.nn.Conv2d(in_channels=48, out_channels=48, kernel_size=(3, 3), stride=(1, 1),
-                                    padding='same')
-        self.enc3 = torch.nn.Conv2d(in_channels=48, out_channels=48, kernel_size=(3, 3), stride=(1, 1),
-                                    padding='same')
-        self.enc4 = torch.nn.Conv2d(in_channels=48, out_channels=48, kernel_size=(3, 3), stride=(1, 1),
-                                    padding='same')
-        self.enc5 = torch.nn.Conv2d(in_channels=48, out_channels=48, kernel_size=(3, 3), stride=(1, 1),
-                                    padding='same')
-        self.enc6 = torch.nn.Conv2d(in_channels=48, out_channels=48, kernel_size=(3, 3), stride=(1, 1),
-                                    padding='same')
-
-        self.dec5 = torch.nn.Conv2d(in_channels=96, out_channels=96, kernel_size=(3, 3), stride=(1, 1),
-                                    padding='same')
-        self.dec5b = torch.nn.Conv2d(in_channels=96, out_channels=96, kernel_size=(3, 3), stride=(1, 1),
-                                     padding='same')
-        self.dec4 = torch.nn.Conv2d(in_channels=144, out_channels=96, kernel_size=(3, 3), stride=(1, 1),
-                                    padding='same')
-        self.dec4b = torch.nn.Conv2d(in_channels=96, out_channels=96, kernel_size=(3, 3), stride=(1, 1),
-                                     padding='same')
-        self.dec3 = torch.nn.Conv2d(in_channels=144, out_channels=96, kernel_size=(3, 3), stride=(1, 1),
-                                    padding='same')
-        self.dec3b = torch.nn.Conv2d(in_channels=96, out_channels=96, kernel_size=(3, 3), stride=(1, 1),
-                                     padding='same')
-        self.dec2 = torch.nn.Conv2d(in_channels=144, out_channels=96, kernel_size=(3, 3), stride=(1, 1),
-                                    padding='same')
-        self.dec2b = torch.nn.Conv2d(in_channels=96, out_channels=96, kernel_size=(3, 3), stride=(1, 1),
-                                     padding='same')
-        self.dec1a = torch.nn.Conv2d(in_channels=97, out_channels=64, kernel_size=(3, 3), stride=(1, 1),
-                                     padding='same')
-        self.dec1b = torch.nn.Conv2d(in_channels=64, out_channels=32, kernel_size=(3, 3), stride=(1, 1),
-                                     padding='same')
-        self.dec1 = torch.nn.Conv2d(in_channels=32, out_channels=1, kernel_size=(3, 3), stride=(1, 1),
-                                    padding='same')
-
-        self.upscale2d = torch.nn.UpsamplingNearest2d(scale_factor=2)
-
-
-    def forward(self,x ,batch_size):
-        """  Defines a class for an autoencoder algorithm for an object (image) x
-
-        An autoencoder is a specific type of feedforward neural networks where the
-        input is the same as the
-        output. It compresses the input into a lower-dimensional code and then 
-        reconstruct the output from this representattion. It is a dimensionality 
-        reduction algorithm
-
-        Parameters
-        ----------
-        x : np.array
-        a numpy array containing image 
-
-        Returns
-        ----------
-        x-n : np.array
-        a numpy array containing the denoised image i.e the image itself minus the noise
-
-        """
-      
-        x=torch.reshape(x, [batch_size, 1, self.height, self.width])
-        skips = [x]
-
-        n = x
-
-        # ENCODER
-        n = self.leaky(self.enc0(n))
-        n = self.leaky(self.enc1(n))
-        n = self.pool(n)
-        skips.append(n)
-
-        n = self.leaky(self.enc2(n))
-        n = self.pool(n)
-        skips.append(n)
-
-        n = self.leaky(self.enc3(n))
-        n = self.pool(n)
-        skips.append(n)
-
-        n = self.leaky(self.enc4(n))
-        n = self.pool(n)
-        skips.append(n)
-
-        n = self.leaky(self.enc5(n))
-        n = self.pool(n)
-        n = self.leaky(self.enc6(n))
-
-
-        # DECODER
-        n = self.upscale2d(n)
-        n = torch.cat((n, skips.pop()), dim=1)
-        n = self.leaky(self.dec5(n))
-        n = self.leaky(self.dec5b(n))
-
-        n = self.upscale2d(n)
-        n = torch.cat((n, skips.pop()), dim=1)
-        n = self.leaky(self.dec4(n))
-        n = self.leaky(self.dec4b(n))
-
-        n = self.upscale2d(n)
-        n = torch.cat((n, skips.pop()), dim=1)
-        n = self.leaky(self.dec3(n))
-        n = self.leaky(self.dec3b(n))
-
-        n = self.upscale2d(n)
-        n = torch.cat((n, skips.pop()), dim=1)
-        n = self.leaky(self.dec2(n))
-        n = self.leaky(self.dec2b(n))
-
-        n = self.upscale2d(n)
-        n = torch.cat((n, skips.pop()), dim=1)
-        n = self.leaky(self.dec1a(n))
-        n = self.leaky(self.dec1b(n))
-
-        n = self.dec1(n)
-
-        return x - n
-
-    def loss_function(self,output,target,batch_size):
-      """ Defines and runs the loss function
-        
-      Parameters
-      ----------
-      output : 
-      target :
-      batch_size :
-
-      Returns
-      ----------
-      loss: float
-          The value of loss given your output, target and batch_size
-
-      """
-
-      M = 10.089038980848645
-      m = -1.429329123112601
- 
-      # ----- loss -----
-      log_hat_R = 2*(output*(M-m)+m)
-      hat_R = torch.exp(log_hat_R)+1e-6 # must be nonzero
-      b_square = torch.square(target)
-      loss = (1/batch_size)*torch.mean( 0.5*log_hat_R+b_square/hat_R  ) #+ tf.losses.get_regularization_loss()
-      return loss
- 
-    def training_step(self, batch,batch_number):
-      
-      """ Train the model with the training set
-
-      Parameters
-      ----------
-      batch : a subset of the training date
-      batch_number : ID identifying the batch
-
-      Returns
-      -------
-      loss : float
-        The value of loss given the batch
-        
-      """
-      M = 10.089038980848645
-      m = -1.429329123112601
-
-      x, y = batch
-      x=x.to(self.device)
-      y=y.to(self.device)
-
-
-        
-
-      if (batch_number%2==0):
-        x=(torch.log(torch.square(x)+1e-3)-2*m)/(2*(M-m))
-        out = self.forward(x,self.batch_size)
-        loss = self.loss_function(out, y,self.batch_size)
-
-      else:
-        y=(torch.log(torch.square(y)+1e-3)-2*m)/(2*(M-m))
-        out = self.forward(y,self.batch_size)
-        loss = self.loss_function(out,x,self.batch_size)
-          
-      return loss
-
-    def validation_step(self, batch,image_num,epoch_num,eval_files,eval_set,sample_dir):
-      """ Test the model with the validation set
-
-      Parameters
-      ----------
-      batch : a subset of data
-      image_num : an ID identifying the feeded image
-      epoch_num : an ID identifying the epoch
-      eval_files : .npy files used for evaluation in training
-      eval_set : directory of dataset used for evaluation in training
-
-      Returns
-      ----------
-      output_clean_image : a np.array
-            
-      """
-
-      image_real_part,image_imaginary_part = batch
-
-      image_real_part=image_real_part.to(self.device) 
-      image_imaginary_part=image_imaginary_part.to(self.device)
-
-      # Normalization
-      image_real_part_normalized=(torch.log(torch.square(image_real_part)+1e-3)-2*m)/(2*(M-m))
-      image_imaginary_part_normalized=(torch.log(torch.square(image_imaginary_part)+1e-3)-2*m)/(2*(M-m))
-            
-      out_real = self.forward(image_real_part_normalized,self.eval_batch_size)
-      out_imaginary = self.forward(image_imaginary_part_normalized,self.eval_batch_size)
-
-      output_clean_image = 0.5*(np.square(denormalize_sar(out_real.cpu().numpy()))+np.square(denormalize_sar(out_imaginary.cpu().numpy())))
-      # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # 
-
-      noisyimage = np.squeeze(np.sqrt(np.square(image_real_part.cpu().numpy())+np.square(image_imaginary_part.cpu().numpy())))
-      outputimage = np.sqrt(np.squeeze(output_clean_image))
-
-      # calculate PSNR
-      psnr = cal_psnr(outputimage, noisyimage)
-      print("img%d PSNR: %.2f" % (image_num , psnr))
-
-      # rename and save
-      imagename = eval_files[image_num].replace(eval_set, "")
-      imagename = imagename.replace('.npy', '_epoch_' + str(epoch_num) + '.npy')
-
-      save_sar_images(outputimage, noisyimage, imagename,sample_dir)
-
-      return output_clean_image
-
-
-    # def optimizer(self):
-    #   return torch.optim.Adam
\ No newline at end of file
diff --git a/tests/sar2sar/train/sample/denoised_lely0_epoch_1.png b/tests/sar2sar/train/sample/denoised_lely0_epoch_1.png
deleted file mode 100644
index 7766b6b..0000000
Binary files a/tests/sar2sar/train/sample/denoised_lely0_epoch_1.png and /dev/null differ
diff --git a/tests/sar2sar/train/utils.py b/tests/sar2sar/train/utils.py
deleted file mode 100644
index a9fdcb7..0000000
--- a/tests/sar2sar/train/utils.py
+++ /dev/null
@@ -1,271 +0,0 @@
-import gc
-import os
-import sys
-
-import numpy as np
-from PIL import Image
-from scipy import special
-from scipy import signal
-from glob import glob
-from merlinsar.train.GenerateDataset import GenerateDataset
-
-basedir = '.'
-
-# DEFINE PARAMETERS OF SPECKLE AND NORMALIZATION FACTOR
-M = 10.089038980848645
-m = -1.429329123112601
-L = 1
-c = (1 / 2) * (special.psi(L) - np.log(L))
-cn = c / (M - m)  # normalized (0,1) mean of log speckle
-
-
-def symetrisation_patch(ima):
-    S = np.fft.fftshift(np.fft.fft2(ima[:,:,0]+1j*ima[:,:,1]))
-    p = np.zeros((S.shape[0])) # azimut (ncol)
-    for i in range(S.shape[0]):
-        p[i] = np.mean(np.abs(S[i,:]))
-    sp = p[::-1]
-    c = np.real(np.fft.ifft(np.fft.fft(p)*np.conjugate(np.fft.fft(sp))))
-    d1 = np.unravel_index(c.argmax(),p.shape[0])
-    d1 = d1[0]
-    shift_az_1 = int(round(-(d1-1)/2))%p.shape[0]+int(p.shape[0]/2)
-    p2_1 = np.roll(p,shift_az_1)
-    shift_az_2 = int(round(-(d1-1-p.shape[0])/2))%p.shape[0]+int(p.shape[0]/2)
-    p2_2 = np.roll(p,shift_az_2)
-    window = signal.gaussian(p.shape[0], std=0.2*p.shape[0])
-    test_1 = np.sum(window*p2_1)
-    test_2 = np.sum(window*p2_2)
-    # make sure the spectrum is symetrized and zeo-Doppler centered
-    if test_1>=test_2:
-        p2 = p2_1
-        shift_az = shift_az_1/p.shape[0]
-    else:
-        p2 = p2_2
-        shift_az = shift_az_2/p.shape[0]
-    S2 = np.roll(S,int(shift_az*p.shape[0]),axis=0)
-
-    q = np.zeros((S.shape[1])) # range (nlin)
-    for j in range(S.shape[1]):
-        q[j] = np.mean(np.abs(S[:,j]))
-    sq = q[::-1]
-    #correlation
-    cq = np.real(np.fft.ifft(np.fft.fft(q)*np.conjugate(np.fft.fft(sq))))
-    d2 = np.unravel_index(cq.argmax(),q.shape[0])
-    d2=d2[0]
-    shift_range_1 = int(round(-(d2-1)/2))%q.shape[0]+int(q.shape[0]/2)
-    q2_1 = np.roll(q,shift_range_1)
-    shift_range_2 = int(round(-(d2-1-q.shape[0])/2))%q.shape[0]+int(q.shape[0]/2)
-    q2_2 = np.roll(q,shift_range_2)
-    window_r = signal.gaussian(q.shape[0], std=0.2*q.shape[0])
-    test_1 = np.sum(window_r*q2_1)
-    test_2 = np.sum(window_r*q2_2)
-    if test_1>=test_2:
-        q2 = q2_1
-        shift_range = shift_range_1/q.shape[0]
-    else:
-        q2 = q2_2
-        shift_range = shift_range_2/q.shape[0]
-
-
-    Sf = np.roll(S2,int(shift_range*q.shape[0]),axis=1)
-    ima2 = np.fft.ifft2(np.fft.ifftshift(Sf))
-    return np.stack((np.real(ima2),np.imag(ima2)),axis=2)
-
-
-def symetrisation_patch_test(real_part,imag_part):
-    S = np.fft.fftshift(np.fft.fft2(real_part[0,:,:,0]+1j*imag_part[0,:,:,0]))
-    p = np.zeros((S.shape[0])) # azimut (ncol)
-    for i in range(S.shape[0]):
-        p[i] = np.mean(np.abs(S[i,:]))
-    sp = p[::-1]
-    c = np.real(np.fft.ifft(np.fft.fft(p)*np.conjugate(np.fft.fft(sp))))
-    d1 = np.unravel_index(c.argmax(),p.shape[0])
-    d1 = d1[0]
-    shift_az_1 = int(round(-(d1-1)/2))%p.shape[0]+int(p.shape[0]/2)
-    p2_1 = np.roll(p,shift_az_1)
-    shift_az_2 = int(round(-(d1-1-p.shape[0])/2))%p.shape[0]+int(p.shape[0]/2)
-    p2_2 = np.roll(p,shift_az_2)
-    window = signal.gaussian(p.shape[0], std=0.2*p.shape[0])
-    test_1 = np.sum(window*p2_1)
-    test_2 = np.sum(window*p2_2)
-    # make sure the spectrum is symetrized and zeo-Doppler centered
-    if test_1>=test_2:
-        p2 = p2_1
-        shift_az = shift_az_1/p.shape[0]
-    else:
-        p2 = p2_2
-        shift_az = shift_az_2/p.shape[0]
-    S2 = np.roll(S,int(shift_az*p.shape[0]),axis=0)
-
-    q = np.zeros((S.shape[1])) # range (nlin)
-    for j in range(S.shape[1]):
-        q[j] = np.mean(np.abs(S[:,j]))
-    sq = q[::-1]
-    #correlation
-    cq = np.real(np.fft.ifft(np.fft.fft(q)*np.conjugate(np.fft.fft(sq))))
-    d2 = np.unravel_index(cq.argmax(),q.shape[0])
-    d2=d2[0]
-    shift_range_1 = int(round(-(d2-1)/2))%q.shape[0]+int(q.shape[0]/2)
-    q2_1 = np.roll(q,shift_range_1)
-    shift_range_2 = int(round(-(d2-1-q.shape[0])/2))%q.shape[0]+int(q.shape[0]/2)
-    q2_2 = np.roll(q,shift_range_2)
-    window_r = signal.gaussian(q.shape[0], std=0.2*q.shape[0])
-    test_1 = np.sum(window_r*q2_1)
-    test_2 = np.sum(window_r*q2_2)
-    if test_1>=test_2:
-        q2 = q2_1
-        shift_range = shift_range_1/q.shape[0]
-    else:
-        q2 = q2_2
-        shift_range = shift_range_2/q.shape[0]
-
-
-    Sf = np.roll(S2,int(shift_range*q.shape[0]),axis=1)
-    ima2 = np.fft.ifft2(np.fft.ifftshift(Sf))
-    ima2 = ima2.reshape(1, np.size(ima2, 0), np.size(ima2, 1), 1)
-    return np.real(ima2), np.imag(ima2)
-
-
-class train_data():
-    def __init__(self, filepath="%s/data/image_clean_pat.npy" % basedir):
-        self.filepath = filepath
-        assert '.npy' in filepath
-        if not os.path.exists(filepath):
-            print("[!] Data file not exists")
-            sys.exit(1)
-
-    def __enter__(self):
-        print("[*] Loading data...")
-        self.data = np.load(self.filepath)
-        np.random.shuffle(self.data)
-        print("[*] Load successfully...")
-        return self.data
-
-    def __exit__(self, type, value, trace):
-        del self.data
-        gc.collect()
-        print("In __exit__()")
-
-
-def load_data(filepath="%s/data/image_clean_pat.npy" % basedir):
-    return train_data(filepath=filepath)
-
-
-def normalize_sar(im):
-    return ((np.log(im+1e-6)-m)/(M-m)).astype(np.float32)
-
-def denormalize_sar(im):
-    return np.exp((np.clip(np.squeeze(im),0,1))*(M-m)+m)
-
-def load_train_data(filepath, patch_size, batch_size, stride_size, n_data_augmentation): #TODO: add control on training data: exit if does not exists
-    datagen = GenerateDataset()
-    imgs = datagen.generate_patches(src_dir=filepath, pat_size=patch_size, step=0,
-                             stride=stride_size, bat_size=batch_size, data_aug_times=n_data_augmentation)
-    return imgs
-
-def load_sar_images(filelist):
-    if not isinstance(filelist, list):
-        im = np.load(filelist)
-        return np.array(im).reshape(1, np.size(im, 0), np.size(im, 1), 2)
-    data = []
-    for file in filelist:
-        im = np.load(file)
-        data.append(np.array(im).reshape(1, np.size(im, 0), np.size(im, 1), 2))
-    return data
-
-
-
-def store_data_and_plot(im, threshold, filename):
-    im = np.clip(im, 0, threshold)
-    im = im / threshold * 255
-    im = Image.fromarray(im.astype('float64')).convert('L')
-    im.save(filename.replace('npy','png'))
-
-
-def save_sar_images(denoised, noisy, imagename, save_dir, groundtruth=None):
-    choices = {'marais1':190.92, 'marais2': 168.49, 'saclay':470.92, 'lely':235.90, 'ramb':167.22,
-           'risoul':306.94, 'limagne':178.43, 'saintgervais':560, 'Serreponcon': 450.0,
-          'Sendai':600.0, 'Paris': 1291.0, 'Berlin': 1036.0, 'Bergen': 553.71,
-          'SDP_Lambesc': 349.53, 'Grand_Canyon': 287.0, 'domancy': 560, 'Brazil': 103.0}
-    threshold = None
-    for x in choices:
-        if x in imagename:
-            threshold = choices.get(x)
-    if threshold is None: threshold = np.mean(noisy) + 3 * np.std(noisy)
-
-    if groundtruth:
-        groundtruthfilename = save_dir+"/groundtruth_"+imagename
-        groundtruthfilename=groundtruthfilename.replace("\\","")
-
-        np.save(groundtruthfilename,groundtruth)
-        store_data_and_plot(groundtruth, threshold, groundtruthfilename)
-
-    denoisedfilename = save_dir + "/denoised_" + imagename
-    denoisedfilename=denoisedfilename.replace("\\","")
-
-    np.save(denoisedfilename, denoised)
-    store_data_and_plot(denoised, threshold, denoisedfilename)
-
-    noisyfilename = save_dir + "/noisy_" + imagename
-    noisyfilename=noisyfilename.replace("\\","")
-
-    np.save(noisyfilename, noisy)
-    store_data_and_plot(noisy, threshold, noisyfilename)
-
-
-def save_real_imag_images(real_part, imag_part, imagename, save_dir):
-    choices = {'marais1': 190.92, 'marais2': 168.49, 'saclay': 470.92, 'lely': 235.90, 'ramb': 167.22,
-               'risoul': 306.94, 'limagne': 178.43, 'saintgervais': 560, 'Serreponcon': 450.0,
-               'Sendai': 600.0, 'Paris': 1291.0, 'Berlin': 1036.0, 'Bergen': 553.71,
-               'SDP_Lambesc': 349.53, 'Grand_Canyon': 287.0, 'Brazil': 103.0}
-    threshold = None
-    for x in choices:
-        if x in imagename:
-            threshold = choices.get(x)
-    if threshold is None: threshold = np.mean(imag_part) + 3 * np.std(imag_part)
-
-    realfilename = save_dir + "/denoised_real_" + imagename
-    realfilename=realfilename.replace("\\","")
-
-    np.save(realfilename, real_part)
-    store_data_and_plot(real_part, threshold, realfilename)
-
-    imagfilename = save_dir + "/denoised_imag_" + imagename
-    imagfilename=imagfilename.replace("\\","")
-
-    np.save(imagfilename, imag_part)
-    store_data_and_plot(imag_part, threshold, imagfilename)
-
-def save_real_imag_images_noisy(real_part, imag_part, imagename, save_dir):
-    choices = {'marais1': 190.92, 'marais2': 168.49, 'saclay': 470.92, 'lely': 235.90, 'ramb': 167.22,
-               'risoul': 306.94, 'limagne': 178.43, 'saintgervais': 560, 'Serreponcon': 450.0,
-               'Sendai': 600.0, 'Paris': 1291.0, 'Berlin': 1036.0, 'Bergen': 553.71,
-               'SDP_Lambesc': 349.53, 'Grand_Canyon': 287.0, 'Brazil': 103.0}
-    threshold = None
-    for x in choices:
-        if x in imagename:
-            threshold = choices.get(x)
-    if threshold is None: threshold = np.mean(np.abs(imag_part)) + 3 * np.std(np.abs(imag_part))
-
-    realfilename = save_dir + "/noisy_real_" + imagename
-    realfilename=realfilename.replace("\\","")
-
-    np.save(realfilename, real_part)
-    store_data_and_plot(np.sqrt(2)*np.abs(real_part), threshold, realfilename)
-
-    imagfilename = save_dir + "/noisy_imag_" + imagename
-    imagfilename=imagfilename.replace("\\","")
-    
-    np.save(imagfilename, imag_part)
-    store_data_and_plot(np.sqrt(2)*np.abs(imag_part), threshold, imagfilename)
-
-
-def cal_psnr(Shat, S):
-    # takes amplitudes in input
-    # Shat: a SAR amplitude image
-    # S:    a reference SAR image
-    P = np.quantile(S, 0.99)
-    res = 10 * np.log10((P ** 2) / np.mean(np.abs(Shat - S) ** 2))
-    return res
-
-