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

Issue:
structured train & test parts for the package



Discussion / hints:

Fix patch:
diff --git a/.gitignore b/.gitignore
index b6e4761..4c915d1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,6 +8,7 @@ __pycache__/
 
 # Distribution / packaging
 .Python
+env/
 build/
 develop-eggs/
 dist/
@@ -20,12 +21,9 @@ parts/
 sdist/
 var/
 wheels/
-pip-wheel-metadata/
-share/python-wheels/
 *.egg-info/
 .installed.cfg
 *.egg
-MANIFEST
 
 # PyInstaller
 #  Usually these files are written by a python script from a template
@@ -40,14 +38,12 @@ pip-delete-this-directory.txt
 # Unit test / coverage reports
 htmlcov/
 .tox/
-.nox/
 .coverage
 .coverage.*
 .cache
 nosetests.xml
 coverage.xml
 *.cover
-*.py,cover
 .hypothesis/
 .pytest_cache/
 
@@ -58,8 +54,6 @@ coverage.xml
 # Django stuff:
 *.log
 local_settings.py
-db.sqlite3
-db.sqlite3-journal
 
 # Flask stuff:
 instance/
@@ -77,38 +71,22 @@ target/
 # Jupyter Notebook
 .ipynb_checkpoints
 
-# IPython
-profile_default/
-ipython_config.py
-
 # pyenv
 .python-version
 
-# pipenv
-#   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
-#   However, in case of collaboration, if having platform-specific dependencies or dependencies
-#   having no cross-platform support, pipenv may install dependencies that don't work, or not
-#   install all needed dependencies.
-#Pipfile.lock
-
-# PEP 582; used by e.g. github.com/David-OConnor/pyflow
-__pypackages__/
-
-# Celery stuff
+# celery beat schedule file
 celerybeat-schedule
-celerybeat.pid
 
 # SageMath parsed files
 *.sage.py
 
-# Environments
+# dotenv
 .env
+
+# virtualenv
 .venv
-env/
 venv/
 ENV/
-env.bak/
-venv.bak/
 
 # Spyder project settings
 .spyderproject
@@ -122,8 +100,7 @@ venv.bak/
 
 # mypy
 .mypy_cache/
-.dmypy.json
-dmypy.json
 
-# Pyre type checker
-.pyre/
+# IDE settings
+.vscode/
+.idea/
diff --git a/.travis.yml b/.travis.yml
index d9f2c60..369e553 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -24,5 +24,5 @@ deploy:
     secure: PLEASE_REPLACE_ME
   on:
     tags: true
-    repo: audreyr/merlin
+    repo: ykemiche/deepdespeckling
     python: 3.8
diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst
index ce6a547..b7b9336 100644
--- a/CONTRIBUTING.rst
+++ b/CONTRIBUTING.rst
@@ -15,7 +15,7 @@ Types of Contributions
 Report Bugs
 ~~~~~~~~~~~
 
-Report bugs at https://github.com/audreyr/merlin/issues.
+Report bugs at https://github.com/ykemiche/deepdespeckling/issues.
 
 If you are reporting a bug, please include:
 
@@ -38,14 +38,14 @@ and "help wanted" is open to whoever wants to implement it.
 Write Documentation
 ~~~~~~~~~~~~~~~~~~~
 
-merlin could always use more documentation, whether as part of the
-official merlin docs, in docstrings, or even on the web in blog posts,
+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/audreyr/merlin/issues.
+The best way to send feedback is to file an issue at https://github.com/ykemiche/deepdespeckling/issues.
 
 If you are proposing a feature:
 
@@ -57,17 +57,17 @@ If you are proposing a feature:
 Get Started!
 ------------
 
-Ready to contribute? Here's how to set up `merlin` for local development.
+Ready to contribute? Here's how to set up `deepdespeckling` for local development.
 
-1. Fork the `merlin` repo on GitHub.
+1. Fork the `deepdespeckling` repo on GitHub.
 2. Clone your fork locally::
 
-    $ git clone git@github.com:your_name_here/merlin.git
+    $ 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 merlin
-    $ cd merlin/
+    $ mkvirtualenv deepdespeckling
+    $ cd deepdespeckling/
     $ python setup.py develop
 
 4. Create a branch for local development::
@@ -79,7 +79,7 @@ Ready to contribute? Here's how to set up `merlin` for local development.
 5. When you're done making changes, check that your changes pass flake8 and the
    tests, including testing other Python versions with tox::
 
-    $ flake8 merlin tests
+    $ flake8 deepdespeckling tests
     $ python setup.py test or pytest
     $ tox
 
@@ -103,7 +103,7 @@ Before you submit a pull request, check that it meets these guidelines:
    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/audreyr/merlin/pull_requests
+   https://travis-ci.com/ykemiche/deepdespeckling/pull_requests
    and make sure that the tests pass for all supported Python versions.
 
 Tips
@@ -112,7 +112,7 @@ Tips
 To run a subset of tests::
 
 
-    $ python -m unittest tests.test_merlin
+    $ python -m unittest tests.test_deepdespeckling
 
 Deploying
 ---------
diff --git a/HISTORY.rst b/HISTORY.rst
index c3980f6..1d56d92 100644
--- a/HISTORY.rst
+++ b/HISTORY.rst
@@ -2,7 +2,7 @@
 History
 =======
 
-0.1.0 (2022-04-11)
+0.1.0 (2022-05-09)
 ------------------
 
 * First release on PyPI.
diff --git a/LICENSE b/LICENSE
index a6baafb..ebdb0d6 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,511 +1,22 @@
- CONTRAT DE LICENCE DE LOGICIEL LIBRE CeCILL
+MIT License
 
+Copyright (c) 2022, youcef kemiche
 
-    Avertissement
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
 
-Ce contrat est une licence de logiciel libre issue d'une concertation
-entre ses auteurs afin que le respect de deux grands principes préside à
-sa rédaction:
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
 
-    * d'une part, le respect des principes de diffusion des logiciels
-      libres: accès au code source, droits étendus conférés aux
-      utilisateurs,
-    * d'autre part, la désignation d'un droit applicable, le droit
-      français, auquel elle est conforme, tant au regard du droit de la
-      responsabilité civile que du droit de la propriété intellectuelle
-      et de la protection qu'il offre aux auteurs et titulaires des
-      droits patrimoniaux sur un logiciel.
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
 
-Les auteurs de la licence CeCILL (pour Ce[a] C[nrs] I[nria] L[ogiciel]
-L[ibre]) sont: 
-
-Commissariat à l'Energie Atomique - CEA, établissement public de
-caractère scientifique technique et industriel, dont le siège est situé
-31-33 rue de la Fédération, 75752 Paris cedex 15.
-
-Centre National de la Recherche Scientifique - CNRS, établissement
-public à caractère scientifique et technologique, dont le siège est
-situé 3 rue Michel-Ange 75794 Paris cedex 16.
-
-Institut National de Recherche en Informatique et en Automatique -
-INRIA, établissement public à caractère scientifique et technologique,
-dont le siège est situé Domaine de Voluceau, Rocquencourt, BP 105, 78153
-Le Chesnay cedex.
-
-
-    Préambule
-
-Ce contrat est une licence de logiciel libre dont l'objectif est de
-conférer aux utilisateurs la liberté de modification et de
-redistribution du logiciel régi par cette licence dans le cadre d'un
-modèle de diffusion en logiciel libre.
-
-L'exercice de ces libertés est assorti de certains devoirs à la charge
-des utilisateurs afin de préserver ce statut au cours des
-redistributions ultérieures.
-
-L'accessibilité au code source et les droits de copie, de modification
-et de redistribution qui en découlent ont pour contrepartie de n'offrir
-aux utilisateurs qu'une garantie limitée et de ne faire peser sur
-l'auteur du logiciel, le titulaire des droits patrimoniaux et les
-concédants successifs qu'une responsabilité restreinte.
-
-A cet égard l'attention de l'utilisateur est attirée sur les risques
-associés au chargement, à l'utilisation, à la modification et/ou au
-développement et à la reproduction du logiciel par l'utilisateur étant
-donné sa spécificité de logiciel libre, qui peut le rendre complexe à
-manipuler et qui le réserve donc à des développeurs ou des
-professionnels avertis possédant des connaissances informatiques
-approfondies. Les utilisateurs sont donc invités à charger et tester
-l'adéquation du Logiciel à leurs besoins dans des conditions permettant
-d'assurer la sécurité de leurs systèmes et/ou de leurs données et, plus
-généralement, à l'utiliser et l'exploiter dans les même conditions de
-sécurité. Ce contrat peut être reproduit et diffusé librement, sous
-réserve de le conserver en l'état, sans ajout ni suppression de clauses.
-
-Ce contrat est susceptible de s'appliquer à tout logiciel dont le
-titulaire des droits patrimoniaux décide de soumettre l'exploitation aux
-dispositions qu'il contient.
-
-
-    Article 1 - DEFINITIONS
-
-Dans ce contrat, les termes suivants, lorsqu'ils seront écrits avec une
-lettre capitale, auront la signification suivante:
-
-Contrat: désigne le présent contrat de licence, ses éventuelles versions
-postérieures et annexes.
-
-Logiciel: désigne le logiciel sous sa forme de Code Objet et/ou de Code
-Source et le cas échéant sa documentation, dans leur état au moment de
-l'acceptation du Contrat par le Licencié.
-
-Logiciel Initial: désigne le Logiciel sous sa forme de Code Source et
-éventuellement de Code Objet et le cas échéant sa documentation, dans
-leur état au moment de leur première diffusion sous les termes du Contrat.
-
-Logiciel Modifié: désigne le Logiciel modifié par au moins une
-Contribution.
-
-Code Source: désigne l'ensemble des instructions et des lignes de
-programme du Logiciel et auquel l'accès est nécessaire en vue de
-modifier le Logiciel.
-
-Code Objet: désigne les fichiers binaires issus de la compilation du
-Code Source.
-
-Titulaire: désigne le ou les détenteurs des droits patrimoniaux d'auteur
-sur le Logiciel Initial
-
-Licencié: désigne le ou les utilisateurs du Logiciel ayant accepté le
-Contrat.
-
-Contributeur: désigne le Licencié auteur d'au moins une Contribution.
-
-Concédant: désigne le Titulaire ou toute personne physique ou morale
-distribuant le Logiciel sous le Contrat.
-
-Contribution: désigne l'ensemble des modifications, corrections,
-traductions, adaptations et/ou nouvelles fonctionnalités intégrées dans
-le Logiciel par tout Contributeur, ainsi que tout Module Interne.
-
-Module: désigne un ensemble de fichiers sources y compris leur
-documentation qui permet de réaliser des fonctionnalités ou services
-supplémentaires à ceux fournis par le Logiciel.
-
-Module Externe: désigne tout Module, non dérivé du Logiciel, tel que ce
-Module et le Logiciel s'exécutent dans des espaces d'adressages
-différents, l'un appelant l'autre au moment de leur exécution.
-
-Module Interne: désigne tout Module lié au Logiciel de telle sorte
-qu'ils s'exécutent dans le même espace d'adressage.
-
-GNU GPL: désigne la GNU General Public License dans sa version 2 ou
-toute version ultérieure, telle que publiée par Free Software Foundation
-Inc.
-
-Parties: désigne collectivement le Licencié et le Concédant.
-
-Ces termes s'entendent au singulier comme au pluriel.
-
-
-    Article 2 - OBJET
-
-Le Contrat a pour objet la concession par le Concédant au Licencié d'une
-licence non exclusive, cessible et mondiale du Logiciel telle que
-définie ci-après à l'article 5 pour toute la durée de protection des 
-droits portant sur ce Logiciel.
-
-
-    Article 3 - ACCEPTATION
-
-3.1 L'acceptation par le Licencié des termes du Contrat est réputée
-acquise du fait du premier des faits suivants:
-
-    * (i) le chargement du Logiciel par tout moyen notamment par
-      téléchargement à partir d'un serveur distant ou par chargement à
-      partir d'un support physique;
-    * (ii) le premier exercice par le Licencié de l'un quelconque des
-      droits concédés par le Contrat.
-
-3.2 Un exemplaire du Contrat, contenant notamment un avertissement
-relatif aux spécificités du Logiciel, à la restriction de garantie et à
-la limitation à un usage par des utilisateurs expérimentés a été mis à
-disposition du Licencié préalablement à son acceptation telle que
-définie à l'article 3.1 ci dessus et le Licencié
-reconnaît en avoir pris connaissance.
-
-
-    Article 4 - ENTREE EN VIGUEUR ET DUREE
-
-
-      4.1 ENTREE EN VIGUEUR
-
-Le Contrat entre en vigueur à la date de son acceptation par le Licencié
-telle que définie en 3.1.
-
-
-      4.2 DUREE
-
-Le Contrat produira ses effets pendant toute la durée légale de
-protection des droits patrimoniaux portant sur le Logiciel.
-
-
-    Article 5 - ETENDUE DES DROITS CONCEDES
-
-Le Concédant concède au Licencié, qui accepte, les droits suivants sur
-le Logiciel pour toutes destinations et pour la durée du Contrat dans
-les conditions ci-après détaillées.
-
-Par ailleurs, si le Concédant détient ou venait à détenir un ou
-plusieurs brevets d'invention protégeant tout ou partie des
-fonctionnalités du Logiciel ou de ses composants, il s'engage à ne pas
-opposer les éventuels droits conférés par ces brevets aux Licenciés
-successifs qui utiliseraient, exploiteraient ou modifieraient le
-Logiciel. En cas de cession de ces brevets, le Concédant s'engage à
-faire reprendre les obligations du présent alinéa aux cessionnaires.
-
-
-      5.1 DROIT D'UTILISATION
-
-Le Licencié est autorisé à utiliser le Logiciel, sans restriction quant
-aux domaines d'application, étant ci-après précisé que cela comporte:
-
-   1. la reproduction permanente ou provisoire du Logiciel en tout ou
-      partie par tout moyen et sous toute forme.
-
-   2. le chargement, l'affichage, l'exécution, ou le stockage du
-      Logiciel sur tout support.
-
-   3. la possibilité d'en observer, d'en étudier, ou d'en tester le
-      fonctionnement afin de déterminer les idées et principes qui sont
-      à la base de n'importe quel élément de ce Logiciel; et ceci,
-      lorsque le Licencié effectue toute opération de chargement,
-      d'affichage, d'exécution, de transmission ou de stockage du
-      Logiciel qu'il est en droit d'effectuer en vertu du Contrat.
-
-
-      5.2 DROIT D'APPORTER DES CONTRIBUTIONS
-
-Le droit d'apporter des Contributions comporte le droit de traduire,
-d'adapter, d'arranger ou d'apporter toute autre modification au Logiciel
-et le droit de reproduire le Logiciel en résultant.
-
-Le Licencié est autorisé à apporter toute Contribution au Logiciel sous
-réserve de mentionner, de façon explicite, son nom en tant qu'auteur de
-cette Contribution et la date de création de celle-ci.
-
-
-      5.3 DROIT DE DISTRIBUTION
-
-Le droit de distribution comporte notamment le droit de diffuser, de
-transmettre et de communiquer le Logiciel au public sur tout support et
-par tout moyen ainsi que le droit de mettre sur le marché à titre
-onéreux ou gratuit, un ou des exemplaires du Logiciel par tout procédé.
-
-Le Licencié est autorisé à distribuer des copies du Logiciel, modifié ou
-non, à des tiers dans les conditions ci-après détaillées.
-
-
-        5.3.1 DISTRIBUTION DU LOGICIEL SANS MODIFICATION
-
-Le Licencié est autorisé à distribuer des copies conformes du Logiciel,
-sous forme de Code Source ou de Code Objet, à condition que cette
-distribution respecte les dispositions du Contrat dans leur totalité et
-soit accompagnée:
-
-   1. d'un exemplaire du Contrat,
-
-   2. d'un avertissement relatif à la restriction de garantie et de
-      responsabilité du Concédant telle que prévue aux articles 8
-      et 9,
-
-et que, dans le cas où seul le Code Objet du Logiciel est redistribué,
-le Licencié permette aux futurs Licenciés d'accéder facilement au Code
-Source complet du Logiciel en indiquant les modalités d'accès, étant
-entendu que le coût additionnel d'acquisition du Code Source ne devra
-pas excéder le simple coût de transfert des données.
-
-
-        5.3.2 DISTRIBUTION DU LOGICIEL MODIFIE
-
-Lorsque le Licencié apporte une Contribution au Logiciel, les conditions
-de distribution du Logiciel Modifié sont alors soumises à l'intégralité
-des dispositions du Contrat.
-
-Le Licencié est autorisé à distribuer le Logiciel Modifié, sous forme de
-Code Source ou de Code Objet, à condition que cette distribution
-respecte les dispositions du Contrat dans leur totalité et soit
-accompagnée:
-
-   1. d'un exemplaire du Contrat,
-
-   2. d'un avertissement relatif à la restriction de garantie et de
-      responsabilité du Concédant telle que prévue aux articles 8
-      et 9,
-
-et que, dans le cas où seul le Code Objet du Logiciel Modifié est
-redistribué, le Licencié permette aux futurs Licenciés d'accéder
-facilement au Code Source complet du Logiciel Modifié en indiquant les
-modalités d'accès, étant entendu que le coût additionnel d'acquisition
-du Code Source ne devra pas excéder le simple coût de transfert des données.
-
-
-        5.3.3 DISTRIBUTION DES MODULES EXTERNES
-
-Lorsque le Licencié a développé un Module Externe les conditions du
-Contrat ne s'appliquent pas à ce Module Externe, qui peut être distribué
-sous un contrat de licence différent.
-
-
-        5.3.4 COMPATIBILITE AVEC LA LICENCE GNU GPL
-
-Le Licencié peut inclure un code soumis aux dispositions d'une des
-versions de la licence GNU GPL dans le Logiciel modifié ou non et
-distribuer l'ensemble sous les conditions de la même version de la
-licence GNU GPL.
-
-Le Licencié peut inclure le Logiciel modifié ou non dans un code soumis
-aux dispositions d'une des versions de la licence GNU GPL et distribuer
-l'ensemble sous les conditions de la même version de la licence GNU GPL.
-
-
-    Article 6 - PROPRIETE INTELLECTUELLE
-
-
-      6.1 SUR LE LOGICIEL INITIAL
-
-Le Titulaire est détenteur des droits patrimoniaux sur le Logiciel
-Initial. Toute utilisation du Logiciel Initial est soumise au respect
-des conditions dans lesquelles le Titulaire a choisi de diffuser son
-oeuvre et nul autre n'a la faculté de modifier les conditions de
-diffusion de ce Logiciel Initial.
-
-Le Titulaire s'engage à ce que le Logiciel Initial reste au moins régi
-par la présente licence et ce, pour la durée visée à l'article 4.2.
-
-
-      6.2 SUR LES CONTRIBUTIONS
-
-Le Licencié qui a développé une Contribution est titulaire sur celle-ci
-des droits de propriété intellectuelle dans les conditions définies par
-la législation applicable.
-
-
-      6.3 SUR LES MODULES EXTERNES
-
-Le Licencié qui a développé un Module Externe est titulaire sur celui-ci
-des droits de propriété intellectuelle dans les conditions définies par
-la législation applicable et reste libre du choix du contrat régissant
-sa diffusion.
-
-
-      6.4 DISPOSITIONS COMMUNES
-
-Le Licencié s'engage expressément:
-
-   1. à ne pas supprimer ou modifier de quelque manière que ce soit les
-      mentions de propriété intellectuelle apposées sur le Logiciel;
-
-   2. à reproduire à l'identique lesdites mentions de propriété
-      intellectuelle sur les copies du Logiciel modifié ou non.
-
-Le Licencié s'engage à ne pas porter atteinte, directement ou
-indirectement, aux droits de propriété intellectuelle du Titulaire et/ou
-des Contributeurs sur le Logiciel et à prendre, le cas échéant, à
-l'égard de son personnel toutes les mesures nécessaires pour assurer le
-respect des dits droits de propriété intellectuelle du Titulaire et/ou
-des Contributeurs.
-
-
-    Article 7 - SERVICES ASSOCIES
-
-7.1 Le Contrat n'oblige en aucun cas le Concédant à la réalisation de
-prestations d'assistance technique ou de maintenance du Logiciel.
-
-Cependant le Concédant reste libre de proposer ce type de services. Les
-termes et conditions d'une telle assistance technique et/ou d'une telle
-maintenance seront alors déterminés dans un acte séparé. Ces actes de
-maintenance et/ou assistance technique n'engageront que la seule
-responsabilité du Concédant qui les propose.
-
-7.2 De même, tout Concédant est libre de proposer, sous sa seule
-responsabilité, à ses licenciés une garantie, qui n'engagera que lui,
-lors de la redistribution du Logiciel et/ou du Logiciel Modifié et ce,
-dans les conditions qu'il souhaite. Cette garantie et les modalités
-financières de son application feront l'objet d'un acte séparé entre le
-Concédant et le Licencié.
-
-
-    Article 8 - RESPONSABILITE
-
-8.1 Sous réserve des dispositions de l'article 8.2, le Licencié a la 
-faculté, sous réserve de prouver la faute du Concédant concerné, de
-solliciter la réparation du préjudice direct qu'il subirait du fait du
-logiciel et dont il apportera la preuve.
-
-8.2 La responsabilité du Concédant est limitée aux engagements pris en
-application du Contrat et ne saurait être engagée en raison notamment:
-(i) des dommages dus à l'inexécution, totale ou partielle, de ses
-obligations par le Licencié, (ii) des dommages directs ou indirects
-découlant de l'utilisation ou des performances du Logiciel subis par le
-Licencié et (iii) plus généralement d'un quelconque dommage indirect. En
-particulier, les Parties conviennent expressément que tout préjudice
-financier ou commercial (par exemple perte de données, perte de
-bénéfices, perte d'exploitation, perte de clientèle ou de commandes,
-manque à gagner, trouble commercial quelconque) ou toute action dirigée
-contre le Licencié par un tiers, constitue un dommage indirect et
-n'ouvre pas droit à réparation par le Concédant.
-
-
-    Article 9 - GARANTIE
-
-9.1 Le Licencié reconnaît que l'état actuel des connaissances
-scientifiques et techniques au moment de la mise en circulation du
-Logiciel ne permet pas d'en tester et d'en vérifier toutes les
-utilisations ni de détecter l'existence d'éventuels défauts. L'attention
-du Licencié a été attirée sur ce point sur les risques associés au
-chargement, à l'utilisation, la modification et/ou au développement et à
-la reproduction du Logiciel qui sont réservés à des utilisateurs avertis.
-
-Il relève de la responsabilité du Licencié de contrôler, par tous
-moyens, l'adéquation du produit à ses besoins, son bon fonctionnement et
-de s'assurer qu'il ne causera pas de dommages aux personnes et aux biens.
-
-9.2 Le Concédant déclare de bonne foi être en droit de concéder
-l'ensemble des droits attachés au Logiciel (comprenant notamment les
-droits visés à l'article 5).
-
-9.3 Le Licencié reconnaît que le Logiciel est fourni "en l'état" par le
-Concédant sans autre garantie, expresse ou tacite, que celle prévue à
-l'article 9.2 et notamment sans aucune garantie sur sa valeur 
-commerciale, son caractère sécurisé, innovant ou pertinent.
-
-En particulier, le Concédant ne garantit pas que le Logiciel est exempt
-d'erreur, qu'il fonctionnera sans interruption, qu'il sera compatible
-avec l'équipement du Licencié et sa configuration logicielle ni qu'il
-remplira les besoins du Licencié.
-
-9.4 Le Concédant ne garantit pas, de manière expresse ou tacite, que le
-Logiciel ne porte pas atteinte à un quelconque droit de propriété
-intellectuelle d'un tiers portant sur un brevet, un logiciel ou sur tout
-autre droit de propriété. Ainsi, le Concédant exclut toute garantie au
-profit du Licencié contre les actions en contrefaçon qui pourraient être
-diligentées au titre de l'utilisation, de la modification, et de la
-redistribution du Logiciel. Néanmoins, si de telles actions sont
-exercées contre le Licencié, le Concédant lui apportera son aide
-technique et juridique pour sa défense. Cette aide technique et
-juridique est déterminée au cas par cas entre le Concédant concerné et
-le Licencié dans le cadre d'un protocole d'accord. Le Concédant dégage
-toute responsabilité quant à l'utilisation de la dénomination du
-Logiciel par le Licencié. Aucune garantie n'est apportée quant à
-l'existence de droits antérieurs sur le nom du Logiciel et sur
-l'existence d'une marque.
-
-
-    Article 10 - RESILIATION
-
-10.1 En cas de manquement par le Licencié aux obligations mises à sa
-charge par le Contrat, le Concédant pourra résilier de plein droit le
-Contrat trente (30) jours après notification adressée au Licencié et
-restée sans effet.
-
-10.2 Le Licencié dont le Contrat est résilié n'est plus autorisé à
-utiliser, modifier ou distribuer le Logiciel. Cependant, toutes les
-licences qu'il aura concédées antérieurement à la résiliation du Contrat
-resteront valides sous réserve qu'elles aient été effectuées en
-conformité avec le Contrat.
-
-
-    Article 11 - DISPOSITIONS DIVERSES
-
-
-      11.1 CAUSE EXTERIEURE
-
-Aucune des Parties ne sera responsable d'un retard ou d'une défaillance
-d'exécution du Contrat qui serait dû à un cas de force majeure, un cas
-fortuit ou une cause extérieure, telle que, notamment, le mauvais
-fonctionnement ou les interruptions du réseau électrique ou de
-télécommunication, la paralysie du réseau liée à une attaque
-informatique, l'intervention des autorités gouvernementales, les
-catastrophes naturelles, les dégâts des eaux, les tremblements de terre,
-le feu, les explosions, les grèves et les conflits sociaux, l'état de
-guerre...
-
-11.2 Le fait, par l'une ou l'autre des Parties, d'omettre en une ou
-plusieurs occasions de se prévaloir d'une ou plusieurs dispositions du
-Contrat, ne pourra en aucun cas impliquer renonciation par la Partie
-intéressée à s'en prévaloir ultérieurement.
-
-11.3 Le Contrat annule et remplace toute convention antérieure, écrite
-ou orale, entre les Parties sur le même objet et constitue l'accord
-entier entre les Parties sur cet objet. Aucune addition ou modification
-aux termes du Contrat n'aura d'effet à l'égard des Parties à moins
-d'être faite par écrit et signée par leurs représentants dûment habilités.
-
-11.4 Dans l'hypothèse où une ou plusieurs des dispositions du Contrat
-s'avèrerait contraire à une loi ou à un texte applicable, existants ou
-futurs, cette loi ou ce texte prévaudrait, et les Parties feraient les
-amendements nécessaires pour se conformer à cette loi ou à ce texte.
-Toutes les autres dispositions resteront en vigueur. De même, la
-nullité, pour quelque raison que ce soit, d'une des dispositions du
-Contrat ne saurait entraîner la nullité de l'ensemble du Contrat.
-
-
-      11.5 LANGUE
-
-Le Contrat est rédigé en langue française et en langue anglaise, ces
-deux versions faisant également foi.
-
-
-    Article 12 - NOUVELLES VERSIONS DU CONTRAT
-
-12.1 Toute personne est autorisée à copier et distribuer des copies de
-ce Contrat.
-
-12.2 Afin d'en préserver la cohérence, le texte du Contrat est protégé
-et ne peut être modifié que par les auteurs de la licence, lesquels se
-réservent le droit de publier périodiquement des mises à jour ou de
-nouvelles versions du Contrat, qui possèderont chacune un numéro
-distinct. Ces versions ultérieures seront susceptibles de prendre en
-compte de nouvelles problématiques rencontrées par les logiciels libres.
-
-12.3 Tout Logiciel diffusé sous une version donnée du Contrat ne pourra
-faire l'objet d'une diffusion ultérieure que sous la même version du
-Contrat ou une version postérieure, sous réserve des dispositions de
-l'article 5.3.4.
-
-
-    Article 13 - LOI APPLICABLE ET COMPETENCE TERRITORIALE
-
-13.1 Le Contrat est régi par la loi française. Les Parties conviennent
-de tenter de régler à l'amiable les différends ou litiges qui
-viendraient à se produire par suite ou à l'occasion du Contrat.
-
-13.2 A défaut d'accord amiable dans un délai de deux (2) mois à compter
-de leur survenance et sauf situation relevant d'une procédure d'urgence,
-les différends ou litiges seront portés par la Partie la plus diligente
-devant les Tribunaux compétents de Paris.
-
-
-Version 2.0 du 2005-05-21.
diff --git a/MANIFEST.in b/MANIFEST.in
index 965b2dd..816b7a9 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -5,7 +5,11 @@ 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
+recursive-include docs *.rst conf.py Makefile make.bat *.jpg *.png *.gif *.pth
diff --git a/Makefile b/Makefile
index 521fe27..236d5ba 100644
--- a/Makefile
+++ b/Makefile
@@ -48,7 +48,7 @@ clean-test: ## remove test and coverage artifacts
 	rm -fr .pytest_cache
 
 lint/flake8: ## check style with flake8
-	flake8 merlin tests
+	flake8 deepdespeckling tests
 
 lint: lint/flake8 ## check style
 
@@ -59,15 +59,15 @@ test-all: ## run tests on every Python version with tox
 	tox
 
 coverage: ## check code coverage quickly with the default Python
-	coverage run --source merlin setup.py test
+	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/merlin.rst
+	rm -f docs/deepdespeckling.rst
 	rm -f docs/modules.rst
-	sphinx-apidoc -o docs/ merlin
+	sphinx-apidoc -o docs/ deepdespeckling
 	$(MAKE) -C docs clean
 	$(MAKE) -C docs html
 	$(BROWSER) docs/_build/html/index.html
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..b5af5f2
--- /dev/null
+++ b/README.md
@@ -0,0 +1,158 @@
+# deepdespeckling for Synthetic Aperture Radar (SAR) images
+
+
+Based on the work of Emanuele Dalsasso, post-doctoral researcher at CNAM and Telecom Paris.
+
+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.
+
+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.
+
+To know more about the researcher's work : https://arxiv.org/abs/2110.13148
+
+To get a test function using Tensorflow's framework : https://gitlab.telecom-paris.fr/ring/MERLIN/-/blob/master/README.md
+
+
+[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
+<img src="icons/pypi.svg" class="center">
+<img src="icons/doc.svg" class="center">
+<img src="icons/build.svg" class="center">
+
+![Python](https://img.shields.io/badge/python-3670A0?style=for-the-badge&logo=python&logoColor=ffdd54)
+
+# Installation
+
+Install merlin by running in the command prompt :
+
+```console
+pip install -i https://test.pypi.org/simple/ deepdespeckling
+```
+
+# Initialization for merlin package.
+
+```python
+import deepdespeckling
+```
+
+
+
+
+# Authors
+
+
+* [Emanuele Dalsasso](https://perso.telecom-paristech.fr/dalsasso/) (Researcher at Telecom Paris)
+* [Youcef Kemiche](https://engineeringteam.hi-paris.fr/about-us-2/) (Hi! PARIS engineer)
+* [Pierre Blanchard](https://engineeringteam.hi-paris.fr/about-us-2/) (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.
+
+1) I have a high-resolution SAR image and I want to apply the despeckling function to the whole of it:
+
+```python
+from deepdespeckling.merlin.test.spotlight import despeckle
+
+image_path="path/to/cosar/image"
+destination_directory="path/where/to/save/results"
+model_weights_path="path/to/model/weights"
+
+despeckle(image_path,destination_directory,model_weights_path=model_weights_path)
+```
+
+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:
+```python
+from deepdespeckling.merlin.test.spotlight import despeckle_from_coordinates
+
+image_path="path/to/cosar/image"
+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(image_path, coordinates_dict, destination_directory, model_weights_path)
+````
+
+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:
+```python
+from deepdespeckling.merlin.test.spotlight import despeckle_from_crop
+
+image_path="path/to/cosar/image"
+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(image_path, destination_directory, model_weights_path, fixed=False)
+```
+* The cropping tool:
+
+<p align="center">
+  <img src="img/crop/crop_example.png" width="66%" class="center">
+</p>
+
+* The results:
+
+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%">
+
+### Train
+
+1) I want to train my own model from scratch:
+```python
+from deepdespeckling.merlin.train.train import create_model, fit_model
+nb_epoch=1
+
+lr = 0.001 * np.ones([nb_epoch])
+lr[6:20] = lr[0]/10
+lr[20:] = lr[0]/100
+seed=1
+
+training_set_directory="path/to/the/training/data"
+validation_set_directory="path/to/the/test/data"
+save_directory="path/where/to/save/results"
+sample_directory="path/to/sample/data"
+from_pretrained=True
+
+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)
+
+```
+
+2I want to train a model using the pre-trained version :
+```python
+from deepdespeckling.merlin.train.train import create_model, fit_model
+from merlinsar.train.model import Model
+
+nb_epoch=1
+
+lr = 0.001 * np.ones([nb_epoch])
+lr[6:20] = lr[0]/10
+lr[20:] = lr[0]/100
+
+training_set_directory="path/to/the/training/data"
+validation_set_directory="path/to/the/test/data"
+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)
+```
+
+# Contribute
+
+
+- Source Code: https://github.com/hi-paris/deepdespeckling.git
+
+# License
+
+* Free software: MIT
+
+# FAQ
+
+* Please contact us at [engineer@hi-paris.fr](engineer@hi-paris.fr)
diff --git a/README.rst b/README.rst
deleted file mode 100644
index a2e89db..0000000
--- a/README.rst
+++ /dev/null
@@ -1,69 +0,0 @@
-Merlin-sar Despeckling Package
-------------------------------
-
-Based on the work of Emanuele Dalsasso, post-doctoral researcher at CNAM and Telecom Paris. 
-
-Speckle fluctuations seriously limit the interpretability of synthetic aperture radar (SAR) images. This package provides a despeckling function that can highly improve the quality and interpretability of SAR images. 
-
-The package contains both test and train parts, wether you wish to despeckle a single pic (test) or urse our model to build ou improve your own. 
-
-To know more about the researcher's work : https://arxiv.org/abs/2110.13148
-
-To get a test function using Tensorflow's framework : https://gitlab.telecom-paris.fr/ring/MERLIN/-/blob/master/README.md
-
-
-.. image:: https://img.shields.io/pypi/v/merlin.svg
-        :target: https://pypi.python.org/pypi/merlin
-
-.. image:: https://img.shields.io/travis/audreyr/merlin.svg
-        :target: https://travis-ci.com/audreyr/merlin
-
-.. image:: https://readthedocs.org/projects/merlin/badge/?version=latest
-        :target: https://merlin.readthedocs.io/en/latest/?version=latest
-        :alt: Documentation Status
-
-
-
-
-Initialization for merlin package.
-----------------------------------
-
-import merlinsar
-
-despeckle
-
-* Free software: CECIL-B
-* Documentation: link will be provided soon.
-
-Authors
--------
-
--Emanuele Dalsasso : Post-doctoral researcher at CNAM and Telecom Paris
-
--Youcef Kemiche : Hi! PARIS machine learning engineer
-
--Pierre Blanchard : Hi! PARIS machine learning engineer
-
-
-Features
---------
-
-- despeckling 
-
-Installation
-------------
-
-Install merlin by running:
-
-	pip install merlin-sar
-	
-
-Contribute
-----------
-
-- Source Code: https://github.com/hi-paris/merlin
-
-FAQ
----
-
-Please contact us at engineer@hi-paris.fr
diff --git a/tests/__init__.py b/deepdespeckling/__init__.py
similarity index 53%
rename from tests/__init__.py
rename to deepdespeckling/__init__.py
index 58583c4..305ecf0 100644
--- a/tests/__init__.py
+++ b/deepdespeckling/__init__.py
@@ -1,1 +1,3 @@
 """Unit test package for merlin."""
+
+import deepdespeckling.merlin
diff --git a/deepdespeckling/merlin/__init__.py b/deepdespeckling/merlin/__init__.py
new file mode 100644
index 0000000..cec0676
--- /dev/null
+++ b/deepdespeckling/merlin/__init__.py
@@ -0,0 +1,3 @@
+"""Unit test package for merlin."""
+
+import deepdespeckling.merlin.train, deepdespeckling.merlin.test
diff --git a/merlinsar/train/.directory b/deepdespeckling/merlin/train/.directory
similarity index 100%
rename from merlinsar/train/.directory
rename to deepdespeckling/merlin/train/.directory
diff --git a/deepdespeckling/merlin/train/Dataset.py b/deepdespeckling/merlin/train/Dataset.py
new file mode 100644
index 0000000..dbb87bc
--- /dev/null
+++ b/deepdespeckling/merlin/train/Dataset.py
@@ -0,0 +1,53 @@
+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/merlinsar/train/GenerateDataset.py b/deepdespeckling/merlin/train/GenerateDataset.py
similarity index 100%
rename from merlinsar/train/GenerateDataset.py
rename to deepdespeckling/merlin/train/GenerateDataset.py
diff --git a/deepdespeckling/merlin/train/__init__.py b/deepdespeckling/merlin/train/__init__.py
new file mode 100644
index 0000000..aed35a5
--- /dev/null
+++ b/deepdespeckling/merlin/train/__init__.py
@@ -0,0 +1,12 @@
+"""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/merlinsar/train/model.py b/deepdespeckling/merlin/train/model.py
similarity index 95%
rename from merlinsar/train/model.py
rename to deepdespeckling/merlin/train/model.py
index f71cbfc..a799a0d 100644
--- a/merlinsar/train/model.py
+++ b/deepdespeckling/merlin/train/model.py
@@ -1,10 +1,7 @@
-import time
 import numpy as np
-import os
 
-from merlinsar.train.utils import *
+from deepdespeckling.merlin.train.utils import *
 from scipy import special
-import argparse
 
 
 # DEFINE PARAMETERS OF SPECKLE AND NORMALIZATION FACTOR
@@ -59,7 +56,7 @@
 
 
 
-class AE(torch.nn.Module):
+class Model(torch.nn.Module):
 
     def __init__(self,batch_size,eval_batch_size,device):
         super().__init__()
@@ -68,17 +65,9 @@ def __init__(self,batch_size,eval_batch_size,device):
         self.eval_batch_size=eval_batch_size
         self.device=device
 
-        self.x = None
-        self.height = None
-        self.width = None
-        self.out_channels = None
-        self.kernel_size_cv2d = None
-        self.stride_cv2d = None
-        self.padding_cv2d = None
-        self.kernel_size_mp2d = None
-        self.stride_mp2d = None
-        self.padding_mp2d = None
-        self.alpha = None
+        self.height = 256
+        self.width = 256
+
         self.pool = torch.nn.MaxPool2d(kernel_size=2, stride=2)
         self.leaky = torch.nn.LeakyReLU(0.1)
 
@@ -143,7 +132,8 @@ def forward(self,x ,batch_size):
         a numpy array containing the denoised image i.e the image itself minus the noise
 
         """
-        x=torch.reshape(x, [batch_size, 1, 256, 256])
+      
+        x=torch.reshape(x, [batch_size, 1, self.height, self.width])
         skips = [x]
 
         n = x
@@ -312,3 +302,5 @@ def validation_step(self, batch,image_num,epoch_num,eval_files,eval_set,sample_d
       return output_clean_image
 
 
+    # def optimizer(self):
+    #   return torch.optim.Adam
\ No newline at end of file
diff --git a/merlinsar/train/sample/denoised_lely0_epoch_1.png b/deepdespeckling/merlin/train/sample/denoised_lely0_epoch_1.png
similarity index 100%
rename from merlinsar/train/sample/denoised_lely0_epoch_1.png
rename to deepdespeckling/merlin/train/sample/denoised_lely0_epoch_1.png
diff --git a/deepdespeckling/merlin/train/saved_model/model.pth b/deepdespeckling/merlin/train/saved_model/model.pth
new file mode 100644
index 0000000..827285b
Binary files /dev/null and b/deepdespeckling/merlin/train/saved_model/model.pth differ
diff --git a/merlinsar/train/main.py b/deepdespeckling/merlin/train/train.py
similarity index 85%
rename from merlinsar/train/main.py
rename to deepdespeckling/merlin/train/train.py
index cb19a9d..75bf228 100644
--- a/merlinsar/train/main.py
+++ b/deepdespeckling/merlin/train/train.py
@@ -7,9 +7,9 @@
 import numpy as np
 import torch
 
-from merlinsar.train.Dataset import *
-from merlinsar.train.model import *
-from merlinsar.train.utils import *
+from deepdespeckling.merlin.train.Dataset import *
+from deepdespeckling.merlin.train.model import *
+from deepdespeckling.merlin.train.utils import *
 
 
 def evaluate(model, loader):
@@ -38,7 +38,7 @@ def save_model(model,destination_folder):
       torch.save(model.state_dict(),destination_folder+"/model.pth")
 
 
-def fit(model,train_loader,val_loader,epochs,lr_list,eval_files,eval_set,sample_dir,seed=2):
+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
@@ -58,14 +58,13 @@ def fit(model,train_loader,val_loader,epochs,lr_list,eval_files,eval_set,sample_
   """
 
   train_losses = []
-  val_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])
+      optimizer=torch.optim.Adam(model.parameters(), lr=lr_list[epoch])
 
       #Train
       for i, batch in enumerate(train_loader, 0):
@@ -76,7 +75,9 @@ def fit(model,train_loader,val_loader,epochs,lr_list,eval_files,eval_set,sample_
 
             loss.backward()
 
-            torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=5.0)
+            if(clip_by_norm==True):
+                  
+              torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=5.0)
 
             optimizer.step()
 
@@ -84,7 +85,6 @@ def fit(model,train_loader,val_loader,epochs,lr_list,eval_files,eval_set,sample_
             print('[%d, %5d] loss: %.3f' % (epoch + 1, i + 1, loss))
             # zero the loss
 
-
           # #Validate
       with torch.no_grad():
         image_num=0
@@ -102,18 +102,19 @@ def create_model(batch_size=12,val_batch_size=1,device=torch.device("cuda:0" if
 
 
   """
-  weights_path="C:/Users/ykemiche/OneDrive - Capgemini/Desktop/fork/merlin/merlin/train/saved_model/model.pth"
+  this_dir, this_filename = os.path.split(__file__)
+  weights_path = os.path.join(this_dir, "saved_model", "model.pth")
 
-  model = AE(batch_size,val_batch_size,device)
+  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):
+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
@@ -140,7 +141,7 @@ def fit_model(model,lr_list,nb_epoch,training_set_directory,validation_set_direc
 
   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,sample_directory)
+  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)
diff --git a/deepdespeckling/merlin/train/utils.py b/deepdespeckling/merlin/train/utils.py
new file mode 100644
index 0000000..469c5ee
--- /dev/null
+++ b/deepdespeckling/merlin/train/utils.py
@@ -0,0 +1,271 @@
+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/sar2sar/train/.directory b/deepdespeckling/sar2sar/train/.directory
new file mode 100644
index 0000000..3fa4480
--- /dev/null
+++ b/deepdespeckling/sar2sar/train/.directory
@@ -0,0 +1,4 @@
+[Dolphin]
+Timestamp=2022,4,5,17,1,21
+Version=4
+ViewMode=1
diff --git a/merlinsar/train/Dataset.py b/deepdespeckling/sar2sar/train/Dataset.py
similarity index 90%
rename from merlinsar/train/Dataset.py
rename to deepdespeckling/sar2sar/train/Dataset.py
index 3d0dec3..c7148c5 100644
--- a/merlinsar/train/Dataset.py
+++ b/deepdespeckling/sar2sar/train/Dataset.py
@@ -8,9 +8,6 @@ class Dataset(torch.utils.data.Dataset):
     'characterizes a dataset for pytorch'
     def __init__(self, patche):
         self.patches = patche
-      
-        # self.patches_MR = patches_MR
-        # self.patches_CT = patches_CT
 
     def __len__(self):
         'denotes the total number of samples'
@@ -35,9 +32,6 @@ class ValDataset(torch.utils.data.Dataset):
     'characterizes a dataset for pytorch'
     def __init__(self, test_set):
         self.files = glob(test_set+'/*.npy')
-      
-        # self.patches_MR = patches_MR
-        # self.patches_CT = patches_CT
 
     def __len__(self):
         'denotes the total number of samples'
diff --git a/deepdespeckling/sar2sar/train/GenerateDataset.py b/deepdespeckling/sar2sar/train/GenerateDataset.py
new file mode 100644
index 0000000..c605613
--- /dev/null
+++ b/deepdespeckling/sar2sar/train/GenerateDataset.py
@@ -0,0 +1,165 @@
+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
new file mode 100644
index 0000000..7f787ab
--- /dev/null
+++ b/deepdespeckling/sar2sar/train/__init__.py
@@ -0,0 +1,12 @@
+"""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
new file mode 100644
index 0000000..bd3025b
--- /dev/null
+++ b/deepdespeckling/sar2sar/train/merlin.py
@@ -0,0 +1,150 @@
+#!/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
new file mode 100644
index 0000000..5f4c2bd
--- /dev/null
+++ b/deepdespeckling/sar2sar/train/model.py
@@ -0,0 +1,306 @@
+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
new file mode 100644
index 0000000..7766b6b
Binary files /dev/null and b/deepdespeckling/sar2sar/train/sample/denoised_lely0_epoch_1.png differ
diff --git a/merlinsar/train/utils.py b/deepdespeckling/sar2sar/train/utils.py
similarity index 93%
rename from merlinsar/train/utils.py
rename to deepdespeckling/sar2sar/train/utils.py
index b96dd02..a9fdcb7 100644
--- a/merlinsar/train/utils.py
+++ b/deepdespeckling/sar2sar/train/utils.py
@@ -6,7 +6,6 @@
 from PIL import Image
 from scipy import special
 from scipy import signal
-import matplotlib.pyplot as plt
 from glob import glob
 from merlinsar.train.GenerateDataset import GenerateDataset
 
@@ -176,8 +175,6 @@ def load_sar_images(filelist):
 
 
 
-
-
 def store_data_and_plot(im, threshold, filename):
     im = np.clip(im, 0, threshold)
     im = im / threshold * 255
@@ -198,14 +195,20 @@ def save_sar_images(denoised, noisy, imagename, save_dir, groundtruth=None):
 
     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)
 
@@ -222,10 +225,14 @@ def save_real_imag_images(real_part, imag_part, imagename, save_dir):
     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)
 
@@ -241,17 +248,17 @@ def save_real_imag_images_noisy(real_part, imag_part, imagename, save_dir):
     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 save_residual(filepath, residual):
-    residual_image = np.squeeze(residual);
-    plt.imsave(filepath, residual_image)
-
 
 def cal_psnr(Shat, S):
     # takes amplitudes in input
@@ -262,14 +269,3 @@ def cal_psnr(Shat, S):
     return res
 
 
-# def tf_psnr(im1, im2):
-#     # assert pixel value range is 0-1
-#     mse = tf.losses.mean_squared_error(labels=im2 * 255.0, predictions=im1 * 255.0)
-#     return 10.0 * (tf.log(255.0 ** 2 / mse) / tf.log(10.0))
-
-"""def tf_psnr(Shat, S):
-    # takes intensities in input
-    S = tf.sqrt(S)
-    Shat = tf.sqrt(Shat)
-    mse = tf.losses.mean_squared_error(labels=S, predictions=Shat)
-    return 10.0 * (tf.log( tfp.stats.percentile(S, 99)/ mse) / tf.log(10.0))"""
diff --git a/docs/Makefile b/docs/Makefile
index b2e2b63..bf630ee 100644
--- a/docs/Makefile
+++ b/docs/Makefile
@@ -4,7 +4,7 @@
 # You can set these variables from the command line.
 SPHINXOPTS    =
 SPHINXBUILD   = python -msphinx
-SPHINXPROJ    = merlin
+SPHINXPROJ    = deepdespeckling
 SOURCEDIR     = .
 BUILDDIR      = _build
 
diff --git a/docs/conf.py b/docs/conf.py
index d1e45a8..0f79b23 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -1,6 +1,6 @@
 #!/usr/bin/env python
 #
-# merlin documentation build configuration file, created by
+# 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
@@ -21,7 +21,7 @@
 import sys
 sys.path.insert(0, os.path.abspath('..'))
 
-import merlin
+import deepdespeckling
 
 # -- General configuration ---------------------------------------------
 
@@ -46,7 +46,7 @@
 master_doc = 'index'
 
 # General information about the project.
-project = 'merlin'
+project = 'deepdespeckling'
 copyright = "2022, youcef kemiche"
 author = "youcef kemiche"
 
@@ -55,9 +55,9 @@
 # the built documents.
 #
 # The short X.Y version.
-version = merlin.__version__
+version = deepdespeckling.__version__
 # The full version, including alpha/beta/rc tags.
-release = merlin.__version__
+release = deepdespeckling.__version__
 
 # The language for content autogenerated by Sphinx. Refer to documentation
 # for a list of supported languages.
@@ -100,7 +100,7 @@
 # -- Options for HTMLHelp output ---------------------------------------
 
 # Output file base name for HTML help builder.
-htmlhelp_basename = 'merlindoc'
+htmlhelp_basename = 'deepdespecklingdoc'
 
 
 # -- Options for LaTeX output ------------------------------------------
@@ -127,8 +127,8 @@
 # (source start file, target name, title, author, documentclass
 # [howto, manual, or own class]).
 latex_documents = [
-    (master_doc, 'merlin.tex',
-     'merlin Documentation',
+    (master_doc, 'deepdespeckling.tex',
+     'deepdespeckling Documentation',
      'youcef kemiche', 'manual'),
 ]
 
@@ -138,8 +138,8 @@
 # One entry per manual page. List of tuples
 # (source start file, name, description, authors, manual section).
 man_pages = [
-    (master_doc, 'merlin',
-     'merlin Documentation',
+    (master_doc, 'deepdespeckling',
+     'deepdespeckling Documentation',
      [author], 1)
 ]
 
@@ -150,10 +150,10 @@
 # (source start file, target name, title, author,
 #  dir menu entry, description, category)
 texinfo_documents = [
-    (master_doc, 'merlin',
-     'merlin Documentation',
+    (master_doc, 'deepdespeckling',
+     'deepdespeckling Documentation',
      author,
-     'merlin',
+     'deepdespeckling',
      'One line description of project.',
      'Miscellaneous'),
 ]
diff --git a/docs/index.rst b/docs/index.rst
index 5204f76..5a14e24 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -1,4 +1,4 @@
-Welcome to merlin's documentation!
+Welcome to deepdespeckling's documentation!
 ======================================
 
 .. toctree::
diff --git a/docs/installation.rst b/docs/installation.rst
index 9983423..5ff1d16 100644
--- a/docs/installation.rst
+++ b/docs/installation.rst
@@ -8,13 +8,13 @@ Installation
 Stable release
 --------------
 
-To install merlin, run this command in your terminal:
+To install deepdespeckling, run this command in your terminal:
 
 .. code-block:: console
 
-    $ pip install merlin
+    $ pip install deepdespeckling
 
-This is the preferred method to install merlin, as it will always install the most recent stable release.
+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.
@@ -26,19 +26,19 @@ you through the process.
 From sources
 ------------
 
-The sources for merlin can be downloaded from the `Github repo`_.
+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/audreyr/merlin
+    $ git clone git://github.com/ykemiche/deepdespeckling
 
 Or download the `tarball`_:
 
 .. code-block:: console
 
-    $ curl -OJL https://github.com/audreyr/merlin/tarball/master
+    $ curl -OJL https://github.com/ykemiche/deepdespeckling/tarball/master
 
 Once you have a copy of the source, you can install it with:
 
@@ -47,5 +47,5 @@ Once you have a copy of the source, you can install it with:
     $ python setup.py install
 
 
-.. _Github repo: https://github.com/audreyr/merlin
-.. _tarball: https://github.com/audreyr/merlin/tarball/master
+.. _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
index 52f20a1..ec3bf90 100644
--- a/docs/make.bat
+++ b/docs/make.bat
@@ -9,7 +9,7 @@ if "%SPHINXBUILD%" == "" (
 )
 set SOURCEDIR=.
 set BUILDDIR=_build
-set SPHINXPROJ=merlin
+set SPHINXPROJ=deepdespeckling
 
 if "%1" == "" goto help
 
diff --git a/docs/readme.rst b/docs/readme.rst
new file mode 100644
index 0000000..72a3355
--- /dev/null
+++ b/docs/readme.rst
@@ -0,0 +1,1 @@
+.. include:: ../README.rst
diff --git a/docs/usage.rst b/docs/usage.rst
new file mode 100644
index 0000000..00ed3b1
--- /dev/null
+++ b/docs/usage.rst
@@ -0,0 +1,7 @@
+=====
+Usage
+=====
+
+To use deepdespeckling in a project::
+
+    import deepdespeckling
diff --git a/icons/build.svg b/icons/build.svg
new file mode 100644
index 0000000..2297d99
--- /dev/null
+++ b/icons/build.svg
@@ -0,0 +1,1 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="98" height="20" role="img" aria-label="build: unknown"><title>build: unknown</title><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="98" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="37" height="20" fill="#555"/><rect x="37" width="61" height="20" fill="#9f9f9f"/><rect width="98" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><text aria-hidden="true" x="195" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="270">build</text><text x="195" y="140" transform="scale(.1)" fill="#fff" textLength="270">build</text><text aria-hidden="true" x="665" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="510">unknown</text><text x="665" y="140" transform="scale(.1)" fill="#fff" textLength="510">unknown</text></g></svg>
\ No newline at end of file
diff --git a/icons/doc.svg b/icons/doc.svg
new file mode 100644
index 0000000..86371bf
--- /dev/null
+++ b/icons/doc.svg
@@ -0,0 +1,1 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="96" height="20"><linearGradient id="b" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="a"><rect width="96" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#a)"><path fill="#555" d="M0 0h35v20H0z"/><path fill="#dfb317" d="M35 0h61v20H35z"/><path fill="url(#b)" d="M0 0h96v20H0z"/></g><g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="110"><text x="185" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="250">docs</text><text x="185" y="140" transform="scale(.1)" textLength="250">docs</text><text x="645" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="510">unknown</text><text x="645" y="140" transform="scale(.1)" textLength="510">unknown</text></g> </svg>
\ No newline at end of file
diff --git a/icons/pypi.svg b/icons/pypi.svg
new file mode 100644
index 0000000..935ace6
--- /dev/null
+++ b/icons/pypi.svg
@@ -0,0 +1,1 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="204" height="20" role="img" aria-label="pypi: package or version not found"><title>pypi: package or version not found</title><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="204" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="33" height="20" fill="#555"/><rect x="33" width="171" height="20" fill="#e05d44"/><rect width="204" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><text aria-hidden="true" x="175" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="230">pypi</text><text x="175" y="140" transform="scale(.1)" fill="#fff" textLength="230">pypi</text><text aria-hidden="true" x="1175" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="1610">package or version not found</text><text x="1175" y="140" transform="scale(.1)" fill="#fff" textLength="1610">package or version not found</text></g></svg>
\ No newline at end of file
diff --git a/img/crop/arrow_down.png b/img/crop/arrow_down.png
new file mode 100644
index 0000000..4c2aa42
Binary files /dev/null and b/img/crop/arrow_down.png differ
diff --git a/img/crop/crop_example.png b/img/crop/crop_example.png
new file mode 100644
index 0000000..9990640
Binary files /dev/null and b/img/crop/crop_example.png differ
diff --git a/merlinsar/train/__init__.py b/merlinsar/train/__init__.py
deleted file mode 100644
index 283db45..0000000
--- a/merlinsar/train/__init__.py
+++ /dev/null
@@ -1,5 +0,0 @@
-"""Top-level package for merlin."""
-
-__author__ = """youcef kemiche"""
-__email__ = 'y.kemiche06@hotmail.com'
-__version__ = '0.1.0'
diff --git a/merlinsar/train/saved_model/model.pth b/merlinsar/train/saved_model/model.pth
deleted file mode 100644
index 4e8e3a2..0000000
Binary files a/merlinsar/train/saved_model/model.pth and /dev/null differ
diff --git a/merlinsar/train/tiilab.py b/merlinsar/train/tiilab.py
deleted file mode 100644
index e792983..0000000
--- a/merlinsar/train/tiilab.py
+++ /dev/null
@@ -1,1251 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Sylvain Lobry 2015
-# JM Nicolas 2015#
-# 
-# Septembre 2017 : on introduit mat2imz
-#   sauvegarde en 8 bits ou en float. Pas d'autre choix
-#   possibilitÃ© de gÃ©nÃ©rer un fichier .hdr en mettant une option
-#
-# Janvier : introduction de dat2mat pour le projet biomass
-"""
-read and display radar images
-"""
-
-
-import numpy as np
-import os.path
-import struct
-import matplotlib.pyplot as plt
-
-from math import sin,cos
-
-#General IO functions
-
-############################################################################
-# Septembre 2017 : une liste de paramÃ¨tres sont requis
-#
-def imz2matbase(namima, listeparam):
-#
-# on rajoute un test sur l'existence mÃªme du fichier
-#
-
-    print('imz2mat, appel imz2matbase : version Septembre 2017')
-
-    try :
-        ftest=open(namima);
-    except IOError:
-        legx=namima+': is a non openable folder'
-        print(legx)
-        print(u'Echec Ã  l\'appel de imz2matbase')
-        return 0,0,0,0
-    ftest.close()
-    
-    nparam=np.size(listeparam)
-    if(nparam!=7):
-        print(u'Echec appel imz2matbase : nombre erronÃ© de paramÃ¨tres (%d'%nparam+' au lieu de 7')
-        return 0,0,0,0
-#    for iut in range(nparam) :
-#        print(listeparam[iut])
-
-##############################################################################
-# Septembre 2017
-#  
-
-
-
-    nblok=    listeparam[0]*listeparam[1]*  listeparam[5]  *  (1+listeparam[6])
-    ncan=0
-    return( _readImage( namima, listeparam[0], listeparam[1], listeparam[2], 1, listeparam[3], listeparam[4], listeparam[5], listeparam[6], nblok, ncan))
-        
-
-def imz2mat(imgName,ncan=0):
-    """
-    lecture d'images plutot radar
-    Formats Telecom et .dat
-    argument 1 : nom du fichier image
-    argument 2 (facultatif) : si multicanal, renvoie uniquement le canal indiquÃ©
-    """
-
-#
-# on rajoute un test dur l'existence mÃªme du fichier
-#
-
-    print(u'imz2mat : version janvier 2018.  Folder to open : %s'%imgName)
-
-    try :
-        ftest=open(imgName);
-    except IOError:
-        legx=imgName+': is a non openable folder'
-        print(legx)
-        print(u'failure to call imz2mat')
-        return 0,0,0,0
-     
-    ftest.close()
-
-##############################################################################
-# Septembre 2017
-#  
-    if imgName.endswith('.dim') : 
-        ncolZ, nligZ, nplantotZ, nzzZ = dimimabase(imgName )
-        offsetZ, nbBytesZ, typeZ, komplexZ, radarZ, endianZ, namima = dimimadim(imgName)
-        print(dimimabase(imgName))
-        print(dimimadim(imgName))
-        nblok=ncolZ*nligZ*nbBytesZ*(1+komplexZ)
-        return( _readImage( namima, ncolZ, nligZ, nplantotZ, nzzZ, offsetZ, endianZ+typeZ, nbBytesZ, komplexZ, nblok, ncan))
- 
-##############################################################################
-# Janvier 2018 : donnÃ©es .dat de l'ONERA (projet Biomass)
-#  
-    if imgName.endswith('.dat') : 
-        return( dat2mat(imgName ))  # voir Ã  la fin de ce fichier, avant les outils de visu
-        
-############################################################################## 
-##############################################################################
-# Septembre 2017
-#  
-        # faire la meme chose avec les .hdr !!!
-###############################################################################
-        
-        
-    ncolZ, nligZ, nplantotZ, nzzZ = dimimabase(imgName )
-
-    if(nplantotZ==1):
-        print("Dans ximaread : image monocanal")
-        
-    if(nplantotZ>0):
-        print("Dans ximaread : lecture du canal "+"%d"%ncan+'/'+'%d'%nplantotZ)
-        
-        
-    """ Reads a file in a xima format. """
-    if imgName.endswith('.ima'):
-        print("image en .ima")
-        return imaread(imgName,ncan)
-    elif imgName.endswith('.IMA'):
-        print("image en .IMA")
-        return imaread(imgName,ncan)
-    elif imgName.endswith('.imw'):
-        print("image en .imw")
-        return imwread(imgName,ncan)
-    elif imgName.endswith('.IMW'):
-        print("image en .IMW")
-        return imwread(imgName,ncan) 
-    elif imgName.endswith('.iml'):
-        print("image en .iml")
-        return imlread(imgName,ncan)
-    elif imgName.endswith('.IML'):
-        print("image en .IML")
-        return imlread(imgName,ncan) 
-    elif imgName.endswith('.rvb'):
-        print("image en .rvb")
-        return imaread(imgName, 1)  ########### TODO
-    elif imgName.endswith('.cxs'):
-        print("image en .cxs")
-        return cxsread(imgName,ncan)
-    elif imgName.endswith('.cxb'):
-        print("image en .cxb")
-        return cxbread(imgName,ncan)
-    elif imgName.endswith('.cxbtivo'):
-        print("image en .cxbtivo")
-        return cxbread(imgName,ncan)
-    elif imgName.endswith('.cxbadts'):
-        print("image en .cxbadts")
-        return cxbread(imgName,ncan)
-    elif imgName.endswith('.CXS'):
-        print("image en .CXS")
-        return cxsread(imgName,ncan)
-    elif imgName.endswith('.cxstivo'):
-        print("image en .cxstivo")
-        return cxsread(imgName,ncan)
-    elif imgName.endswith('.CXSTIVO'):
-        print("image en .CXSTIVO")
-        return cxsread(imgName,ncan)
-    elif imgName.endswith('.cxsadts'):
-        print("image en .cxsadts")
-        return cxsread(imgName,ncan)
-    elif imgName.endswith('.CXSADTS'):
-        print("image en .CXSADTS")
-        return cxsread(imgName,ncan)
-    elif imgName.endswith('.imf'):
-        print("image en .imf")
-        return imfread(imgName,ncan)
-    elif imgName.endswith('.IMF'):
-        print("image en .IMF")
-        return imfread(imgName,ncan)
-    elif imgName.endswith('.imd'):
-        print("image en .imd")
-        return imdread(imgName,ncan)
-    elif imgName.endswith('.IMD'):
-        print("image en .IMD")
-        return imdread(imgName,ncan)
-    elif imgName.endswith('.cxf'):
-        print("image en .cxf")
-        return cxfread(imgName,ncan)
-    elif imgName.endswith('.CXF'):
-        print("image en .CXF")
-        return cxfread(imgName,ncan)
-    elif imgName.endswith('.cxftivo'):
-        print("image en .cxftivo")
-        return cxfread(imgName,ncan)
-    elif imgName.endswith('.CXFTIVO'):
-        print("image en .CXFTIVO")
-        return cxfread(imgName,ncan)
-    elif imgName.endswith('.cxfadts'):
-        print("image en .cxfadts")
-        return cxfread(imgName,ncan)
-    elif imgName.endswith('.CXFADTS'):
-        print("image en .CXFADTS")
-    else:
-#        raise Exception("Format not currently supported.")
-        print("Format non pris en compte actuellement");
-        return 0,0,0,0,0
-#IO operations for ima format. both IMA and ima
-def imaread(imgName,ncan):
-    """ Reads a *ima file. ImgName can be with or without extension. """
-    if imgName.endswith('.ima'):
-        print("image en .ima")
-        extension='.ima'
-    if imgName.endswith('.IMA'):
-        print("image en .IMA")
-        extension='.IMA'
-    if imgName.endswith('.rvb'):
-        print("image en .rvb")
-        extension='.rvb'
-    imgName = os.path.splitext(imgName)[0]
-    return _imaread(imgName, extension,ncan)
-#IO operations for imw format.
-def imwread(imgName,ncan):
-    """ Reads a *.imw file. ImgName can be with or without extension. """
-    if imgName.endswith('.imw'):
-        print("image en .imw")
-        extension='.imw'
-    if imgName.endswith('.IMW'):
-        print("image en .IMW")
-        extension='.IMW'
-    imgName = os.path.splitext(imgName)[0]
-    return _imwread(imgName, extension,ncan)
-#IO operations for iml format.
-def imlread(imgName,ncan):
-    """ Reads a *.iml file. ImgName can be with or without extension. """
-    if imgName.endswith('.iml'):
-        print("image en .iml")
-        extension='.iml'
-    if imgName.endswith('.IML'):
-        print("image en .IML")
-        extension='.IML'
-    imgName = os.path.splitext(imgName)[0]
-    return _imlread(imgName, extension,ncan)
-#IO operations for cxb format.
-def cxbread(imgName,ncan):
-    """ Reads a *.imw file. ImgName can be with or without extension. """
-    if imgName.endswith('.cxb'):
-        print("image en .cxb")
-        extension='.cxb'
-    if imgName.endswith('.cxbtivo'):
-        print("image en .cxbtivo")
-        extension='.cxbtivo'
-    if imgName.endswith('.cxbadts'):
-        print("image en .cxbadts")
-        extension='.cxbadts'
-    imgName = os.path.splitext(imgName)[0]
-    return _cxbread(imgName, extension,ncan)
-#IO operations for cxs format.
-def cxsread(imgName,ncan):
-    """ Reads a *.cxs file. ImgName can be with or without extension. """
-    if imgName.endswith('.cxs'):
-        print("image en .cxs")
-        extension='.cxs'
-    if imgName.endswith('.CXS'):
-        print("image en .CXS")
-        extension='.CXS'
-    if imgName.endswith('.cxstivo'):
-        print("image en .cxstivo")
-        extension='.cxstivo'
-    if imgName.endswith('.CXSTIVO'):
-        print("image en .CXSTIVO")
-        extension='.CXSTIVO'
-    if imgName.endswith('.cxsadts'):
-        print("image en .cxsadts")
-        extension='.cxsadts'
-    if imgName.endswith('.CXSADTS'):
-        print("image en .CXSADTS")
-        extension='.CXSADTS'
-    imgName = os.path.splitext(imgName)[0]
-    return _cxsread(imgName, extension,ncan)
-#IO operations for imf format.
-def imfread(imgName,ncan):
-    """ Reads a *.imf file. ImgName can be with or without extension. """
-    if imgName.endswith('.imf'):
-        print("image en .imf")
-        extension='.imf'
-    if imgName.endswith('.IMF'):
-        print("image en .IMF")
-        extension='.IMF'
-    imgName = os.path.splitext(imgName)[0]
-    return _imfread(imgName, extension,ncan)
-#IO operations for imd format.
-def imdread(imgName,ncan):
-    """ Reads a *.imf file. ImgName can be with or without extension. """
-    if imgName.endswith('.imd'):
-        print("image en .imd")
-        extension='.imd'
-    if imgName.endswith('.IMD'):
-        print("image en .IMD")
-        extension='.IMD'
-    imgName = os.path.splitext(imgName)[0]
-    return _imdread(imgName, extension,ncan)
-#IO operations for cxf format.
-def cxfread(imgName,ncan):
-    """ Reads a *.cxf file. ImgName can be with or without extension. """
-    if imgName.endswith('.cxf'):
-        print("image en .cxf")
-        extension='.cxf'
-    if imgName.endswith('.CXF'):
-        print("image en .CXF")
-        extension='.CXF'
-    if imgName.endswith('.cxftivo'):
-        print("image en .cxftivo")
-        extension='.cxftivo'
-    if imgName.endswith('.CXFTIVO'):
-        print("image en .CXFTIVO")
-        extension='.CXFTIVO'
-    if imgName.endswith('.cxfadts'):
-        print("image en .cxfadts")
-        extension='.cxfadts'
-    if imgName.endswith('.CXFADTS'):
-        print("image en .CXFADTS")
-        extension='.CXFADTS'
-    imgName = os.path.splitext(imgName)[0]
-    return _cxfread(imgName, extension,ncan)
-#Internal functions.
-def _imaread(imgName, extension, ncan):
-    """ Reads a *.ima file. imgName should come with no extension. """
-        #
-
-    w, h, nk, nktemps = _readDimZ(imgName + '.dim')
-    if w==0:
-        print('le fichier .dim n''est pas lisible')
-        return 0,0,0,0,0
-    print('image en .ima ',w, ' ',h,'  canaux:',nk,' verif : ', nktemps)
-    nbBytes=1
-    offset=0
-    type='B'
-    komplex=0
-    nblok=w*h
-    indien = '>' #en minuscule
-    
-    if extension == '.ima':
-        indien='>'
-    if extension == '.IMA':
-        indien='<'
-    if extension == '.rvb':
-        komplex=999
-        nbBytes=1
-    if nktemps>0:
-        offset, nbBytes, type, komplex, radar, indienZ, namerien = _readDimparamZV2(imgName + '.dim')
-        if indienZ != 'Z':
-            indien=indienZ
-    nblok=nblok*nbBytes*(1+komplex) ######## ATTENTION : ne marchera que pour des tivoli
-    return _readImage(imgName + extension, w, h, nk, nktemps, offset, indien+type, nbBytes, komplex, nblok, ncan)
-    
-    
-    
-def _imwread(imgName, extension, ncan):
-    """ Reads a *.imw file. imgName should come with no extension. """
-    w, h, nk, nktemps = _readDimZ(imgName + '.dim')
-    if w==0:
-        print('le fichier .dim n''est pas lisible')
-        return 0,0,0,0,0
-    print('image en unsigned short',w, ' ',h,'  canaux:',nk,' verif : ', nktemps)
-    nbBytes=2
-    offset=0
-    if extension == '.imw':
-        typeA='H'
-        endian='>'
-    if extension == '.IMW':
-        typeA='H'
-        endian='<'
-    komplex=0
-    nblok=w*h*2
-    return _readImage(imgName + extension, w, h, nk, nktemps, offset, endian+typeA, nbBytes, komplex, nblok, ncan)
-def _imlread(imgName, extension, ncan):
-    """ Reads a *.iml file. imgName should come with no extension. """
-    w, h, nk, nktemps = _readDimZ(imgName + '.dim')
-    if w==0:
-        print('le fichier .dim n''est pas lisible')
-        return 0,0,0,0,0
-    print('image en int',w, ' ',h,'  canaux:',nk,' verif : ', nktemps)
-    nbBytes=4
-    offset=0
-    if extension == '.iml':
-        typeA='i'
-        endian='>'
-    if extension == '.IML':
-        typeA='i'
-        endian='<'
-    komplex=0
-    nblok=w*h*4
-    return _readImage(imgName + extension, w, h, nk, nktemps, offset, endian+typeA, nbBytes, komplex, nblok, ncan)
-def _imfread(imgName, extension, ncan):
-    """ Reads a *.imf file. imgName should come with no extension. """
-    w, h, nk, nktemps = _readDimZ(imgName + '.dim')
-    if w==0:
-        print('le fichier .dim n''est pas lisible')
-        return 0,0,0,0,0
-    print('image en float',w, ' ',h,'  canaux:',nk,' verif : ', nktemps)
-    nbBytes=4
-    offset=0
-    if extension == '.imf':
-        typeA='f'
-        endian='>'
-    if extension == '.IMF':
-        typeA='f'
-        endian='<'
-    komplex=0
-    nblok=w*h*4
-    return _readImage(imgName + extension, w, h, nk, nktemps, offset, endian+typeA, nbBytes, komplex, nblok, ncan)
-def _imdread(imgName, extension, ncan):
-    """ Reads a *.imd file. imgName should come with no extension. """
-    w, h, nk, nktemps = _readDimZ(imgName + '.dim')
-    if w==0:
-        print('le fichier .dim n''est pas lisible')
-        return 0,0,0,0,0
-    print('image en double',w, ' ',h,'  canaux:',nk,' verif : ', nktemps)
-    nbBytes=8
-    offset=0
-    if extension == '.imd':
-        typeA='d'
-        endian='>'
-    if extension == '.IMD':
-        typeA='d'
-        endian='<'
-    komplex=0
-    nblok=w*h*4
-    return _readImage(imgName + extension, w, h, nk, nktemps, offset, endian+typeA, nbBytes, komplex, nblok, ncan)
-def _cxbread(imgName, extension, ncan):
-    """ Reads a *.cxb file. imgName should come with no extension. """
-    w, h, nk, nktemps = _readDimZ(imgName + '.dim')
-    if w==0:
-        print('le fichier .dim n''est pas lisible')
-        return 0,0,0,0,0
-    print('image en complex signed char',w, ' ',h,'  canaux:',nk,' verif : ', nktemps)
-    nbBytes=1
-    offset=0
-    nblok=w*h*2
-    if extension == '.cxb':
-        typeA='b'
-        endian='>'
-        komplex=1
-    if extension == '.cxbtivo':
-        typeA='b'
-        endian='>'
-        komplex=2
-    if extension == '.cxbadts':
-        typeA='b'
-        endian='>'
-        komplex=3
-    if nktemps>0:
-        offset, nbBytes, type, komplex, radar, indien, namerien = _readDimparamZV2(imgName + '.dim')
-    if radar==1:
-        komplex=11
-    return _readImage(imgName + extension, w, h, nk, nktemps, offset, type, nbBytes, komplex, nblok, ncan)
-def _cxsread(imgName, extension, ncan):
-    """ Reads a *.cxs file. imgName should come with no extension. """
-    w, h, nk, nktemps = _readDimZ(imgName + '.dim')
-    if w==0:
-        print('le fichier .dim n''est pas lisible')
-        return 0,0,0,0,0
-    print('image en complex signed short',w, ' ',h,'  canaux:',nk,' verif : ', nktemps)
-    nbBytes=2
-    offset=0
-    nblok=w*h*4
-    if extension == '.cxs':
-        typeA='h'
-        endian='>'
-        komplex=1
-    if extension == '.CXS':
-        typeA='h'
-        endian='<'
-        komplex=1
-    if extension == '.cxstivo':
-        typeA='h'
-        endian='>'
-        komplex=2
-    if extension == '.CXSTIVO':
-        typeA='h'
-        endian='<'
-        komplex=2
-    if extension == '.cxsadts':
-        typeA='h'
-        endian='>'
-        komplex=3
-    if extension == '.CXSADTS':
-        typeA='h'
-        endian='<'
-        komplex=3
-    return _readImage(imgName + extension, w, h, nk, nktemps, offset, endian+typeA, nbBytes, komplex, nblok, ncan)
-
-def _cxfread(imgName, extension, ncan):
-    """ Reads a *.cxf file. imgName should come with no extension. """
-    w, h, nk, nktemps = _readDimZ(imgName + '.dim')
-    if w==0:
-        print('le fichier .dim n''est pas lisible')
-        return 0,0,0,0,0
-    print('image en float',w, ' ',h,'  canaux:',nk,' verif : ', nktemps)
-    nbBytes=4
-    offset=0
-    if extension == '.cxf':
-        typeA='f'
-        endian='>'
-        komplex=1
-    if extension == '.CXF':
-        typeA='f'
-        endian='<'
-        komplex=1
-    if extension == '.cxftivo':
-        typeA='f'
-        endian='>'
-        komplex=2
-    if extension == '.CXFTIVO':
-        typeA='f'
-        endian='<'
-        komplex=2
-    if extension == '.cxfadts':
-        typeA='f'
-        endian='>'
-        komplex=3
-    if extension == '.CXFADTS':
-        typeA='f'
-        endian='<'
-        komplex=3
-    nblok=w*h*8
-    return _readImage(imgName + extension, w, h, nk, nktemps, offset, endian+typeA, nbBytes, komplex, nblok, ncan)
-    
-def _readDimZ(dimFile):
-    """ Reads a *.dim file and return width and height. """
-    try :
-        f=open(dimFile);
-    except IOError:
-        legx=dimFile+': est un fichier non ouvrable'
-        print(legx)
-        return 0,0,0,0
-    else:
-        tmp = f.readline().split()
-        w = int(tmp[0])
-        h = int(tmp[1])
-        nk=1
-        nktemps=0 
-# sert de code retour si le fichier .dim n'a que 2 valeurs 
-        if len(tmp)>2:
-            print('Fichier .dim version longue (lecture 3eme parametre) ')
-            nk = int(tmp[2]);
-        if len(tmp)>3:
-            print('Fichier .dim version longue (lecture 4eme parametre)')
-            nktemps = int(tmp[3]);
-        return w, h, nk, nktemps
-        
-def _readDimparamZV2(dimFile):
-    """ Reads a *.dim file and return width and height. """     
-    offsetZ=0
-    nbBytesZ=1
-    typeZ='B'
-    komplexZ=0
-    radarZ=0
-    endianZ='Z'
-    namima=""
-    with open(dimFile) as f:
-        tmpK = f.readline()
-        while tmpK!='':
-            tmp = tmpK.split()
-            print(tmp[0], tmp[1])
-            if tmp[0]=="-offset":
-                # print("Lecture de l''offset  ",tmp[1])
-                offsetZ=tmp[1]
-            if tmp[0]=="-radar":
-                # print("Lecture du radar  ",tmp[1])
-                if tmp[1]=="ERS":
-                    radarZ=1
-            if tmp[0]=="-image":
-                print("Le .dim contient le nom de l\'image : ",tmp[1])
-                namima=tmp[1]
-            if tmp[0]=="-bo":  # bug corrigÃ© !!!
-                # print("endian ?? :   ",tmp[1])
-                if tmp[1]=="SUN":
-                    endianZ='>'
-                if tmp[1]=="DEC":
-                    endianZ='<'
-            if tmp[0]=="-type":
-                # print("Lecture du type  ",tmp[1])
-                if tmp[1]=="U8":
-                    nbBytesZ=1
-                    typeZ='B'
-                if tmp[1]=="U16":
-                    nbBytesZ=2
-                    typeZ='H'
-                if tmp[1]=="S16":
-                    nbBytesZ=2
-                    typeZ='h'
-                if tmp[1]=="S32":
-                    nbBytesZ=4
-                    typeZ='i'
-                if tmp[1]=="U32":
-                    nbBytesZ=4
-                    typeZ='I'
-                if tmp[1]=="FLOAT":
-                    nbBytesZ=4
-                    typeZ='f'
-                if tmp[1]=="DOUBLE":
-                    nbBytesZ=8
-                    typeZ='d'
-                if tmp[1]=="C8":
-                    nbBytesZ=1
-                    komplexZ=1
-                    typeZ='b'
-                if tmp[1]=="CS8":
-                    nbBytesZ=1
-                    komplexZ=1
-                    typeZ='b'
-                if tmp[1]=="CS8TIVO":
-                    nbBytesZ=1
-                    komplexZ=2
-                    typeZ='b'
-                if tmp[1]=="CS8ADTS":
-                    nbBytesZ=1
-                    komplexZ=3
-                    typeZ='b'
-                if tmp[1]=="CS16":
-                    nbBytesZ=2
-                    komplexZ=1
-                    typeZ='h'
-                if tmp[1]=="CS16TIVO":
-                    nbBytesZ=2
-                    komplexZ=2
-                    typeZ='h'
-                if tmp[1]=="CS16ADTS":
-                    nbBytesZ=2
-                    komplexZ=3
-                    typeZ='h'
-                if tmp[1]=="C32TIVO":
-                    nbBytesZ=4
-                    komplexZ=2
-                    typeZ='f'
-                if tmp[1]=="C32ADTS":
-                    nbBytesZ=4
-                    komplexZ=3
-                    typeZ='f'
-                if tmp[1]=="CFLOAT":
-                    nbBytesZ=4
-                    komplexZ=1
-                    typeZ='f'
-                    
-            tmpK = f.readline()  
-
-                
-        return offsetZ, nbBytesZ, typeZ, komplexZ, radarZ, endianZ, namima
-        
-        
-def _readImage(imgName, w, h, nkparam, nktemps, offset, typeA, nbBytes, komplex, nblok, ncan):
-    print("lecture de ", imgName,' en quelconque', w, h, nkparam, ' offset ', offset, typeA, nbBytes,' complex',komplex,'blocksize',nblok)
-    if ncan>0:
-        print('lecture specifique du canal %d'%ncan)
-#    print('parametre ncan : %d'%ncan)
-    """ Reads an image coded in any binary format. """
-    
-    tagRNSAT=0
-    nk=nkparam
-    
-    if  nkparam<0 :
-        print(u'Fichier RNSat : procÃ©dure en test')
-        nk=-nkparam
-        tagRNSAT=1
-    
-    try :
-        f=open(imgName,'rb');
-    except IOError:
-        legx=imgName+': est un fichier non ouvrable'
-        print(legx)
-        return 0,0,0,0,0
-    else:
-        f.seek(offset,0)
-        if komplex==999:
-            imgligne=np.empty([3*w])
-            img = np.empty([h, w, 3])
-            for i in range(0, h):
-#                if i%100==0:
-#                    print(u'Ligne lue %d'%i)
-                record=f.read(nbBytes*3*w)
-                imgligne = np.ndarray( 3*w, typeA, record)
-                img[i, 0:h, 0]= imgligne[0:3*h:3]/255.
-                img[i, 0:h, 1]= imgligne[1:3*h:3]/255.
-                img[i, 0:h, 2]= imgligne[2:3*h:3]/255.
-
-#            imgligne=np.empty([3*w])
-#            for i in range(0, h):
-#                for j in range(0, 3*w):
-#                    imgligne[j] = struct.unpack( typeA, f.read(nbBytes))[0]
-#                for j in range(0, w):
-#                    img[i, j, 0]=imgligne[3*j]/255.
-#                    img[i, j, 1]=imgligne[3*j+1]/255.
-#                    img[i, j, 2]=imgligne[3*j+2]/255.
-            return img,w,h,nk,nktemps
-  
-      
-        if nk>1:
-            tag3=1
-            
-        if nk==1 :
-            tag3=0 
-            nkmin=0
-            nkmax=1    
-            
-        if  ncan==0:
-            tag3=0 
-            nkmin=0
-            nkmax=nk
-            if nkmax>1:
-                tag3=1
-#            print(nkmin)
-#            print(nkmax)
-#            print(tag3)
-            
-        if ncan>0:
-            if ncan>nk:
-                ncan=nk
-            tag3=2
-            nkmin=ncan-1
-            nkmax=ncan
-
-
-        if(ncan>1):
-            f.seek(nblok*(ncan-1))
-            
-        if tag3==1:
-            nkmin=0
-            nkmax=nk
-            if komplex==0:
-                imgtot = np.empty([h, w, nk])
-            else:
-                imgtot = np.zeros([h, w, nk])+ 1j * np.zeros([h, w, nk])
-            
-            if tagRNSAT==1 :
-                if komplex==0:
-                    imgtotstep = np.empty([h*w*nk])
-                if komplex==1:
-                    imgtotstep = np.zeros([h*w*nk]) + 1j * np.zeros([h*w*nk])
-                iutrnsat=0
-                iblocRNSAT=h*w
-            
-            
-        if komplex==0:
-            img = np.empty([h, w])
-            
-        if komplex==1 or komplex==2 or komplex==11:
-            #print(h,w)
-            imgligne=np.empty([2*w])
-            img = np.zeros([h, w])+ 1j * np.zeros([h, w])
-            
-        if komplex==3:
-            imgampli = np.empty([h, w])
-            imgphase = np.empty([h, w])
-            img = np.zeros([h, w])+ 1j * np.zeros([h, w])
-            
-#        print('Boucle de lecture entre %d'%nkmin+' et %d'%nkmax+'   sur %d'%nk+' canaux'+'  (tag3=%d'%tag3+')')           
-        if nk>1:
-            print('Boucle de lecture entre %d'%nkmin+' et %d'%nkmax+'   sur %d'%nk+' canaux')
-        
-#        print('Verif typeA = %s'%typeA )
- 
-
-        for nkz in range(nkmin,nkmax):
-            if tag3==1 or tag3==2:
-                print('Lecture du canal %d'%(nkz+1)+'/%d'%nk)
-#            else:
-#                print('Lecture monocanal  %d'%(nkz+1)+'/%d'%nk)
-                
-#############                
-            if komplex==0:           
-                print(u'DonnÃ©es rÃ©elles. Nouvelle version de imz2mat  '+'%s'%typeA)
-                record=np.zeros(nbBytes*w, dtype=np.byte() )             
-                for i in range(0, h):
-                    record = f.read(nbBytes*w)
-                    img[i, :] = np.ndarray( w, typeA, record)
-#                    for j in range(0, w):
-#                        img[i, j] = struct.unpack( typeA, record[nbBytes*j:nbBytes*j+nbBytes])[0]
-
-############  komplex > 0  : trois cas 
-                    
-            if komplex==1 or komplex==11:   # cas des complexes standards :
-                                            # partie rÃ©elle puis partie imaginaire
-#                for i in range(0, h):
-#                    for j in range(0, 2*w):
-#                        imgligne[j] = struct.unpack( typeA, f.read(nbBytes))[0]
-#                    for j in range(0, w):
-#                        img[i, j] = imgligne[2*j]+imgligne[2*j+1]*1j
-            
-#>>>>>>>>>>>>  a incorporer !!
-#   imgligneZ =  numpy.ndarray( 2*nfencboucle, '<h', record[4*vigdebcol:4*(vigdebcol+nfencboucle)]) 
-                
-                print(u'DonnÃ©es complexes (standard). Nouvelle version de imz2mat  '+'%s'%typeA)
-                record=np.zeros(nbBytes*w*2, dtype=np.byte() )                       
-                for i in range(0, h):
-                    record = f.read(nbBytes*w*2)
-                    imgligne =  np.ndarray( 2*w, typeA, record)
-                    img[i,:]=imgligne[0:2*w:2]+1j*imgligne[1:2*w:2]
-#                    for j in range(0, w):
-#                        img[i, j] = imgligne[2*j]+imgligne[2*j+1]*1j
-                    
-            if komplex==11:
-                valmoyR = np.mean(img.real)
-                valmoyI = np.mean(img.imag)
-                img = img.real-valmoyR+(img.imag-valmoyI)*1j                
-## komplex==2                  
-            if komplex==2:  # d'abord la partie rÃ©elle, puis la partir imaginaire
-                for i in range(0, h):
-                    for j in range(0, w):
-                        imgligne[j] = struct.unpack( typeA, f.read(nbBytes))[0]
-                    for j in range(0, w):
-                        img[i, j] =  imgligne[j]
-                for i in range(0, h):
-                    for j in range(0, w):
-                        imgligne[j] = struct.unpack( typeA, f.read(nbBytes))[0]
-                    for j in range(0, w):
-                        img[i, j] =  img[i, j] + imgligne[j]*1j           
-## komplex==3                        
-            if komplex==3: # d'abord l'amplitude, puis la phase
-                imgampli = np.empty([h, w])
-                imgphase = np.empty([h, w])
-                for i in range(0, h):
-                    for j in range(0, w):
-                        imgampli[i, j] = struct.unpack( typeA, f.read(nbBytes))[0]
-                for i in range(0, h):
-                    for j in range(0, w):
-                        imgphase[i, j] = struct.unpack( typeA, f.read(nbBytes))[0]
-                for i in range(0, h):
-                    for j in range(0, w):
-                        img[i, j] = imgampli[i, j]*(cos(imgphase[i, j])+sin(imgphase[i, j])*1j)
-###################################################################################
-                        
-                        
-            if tag3==1 and tagRNSAT==0:
-                imgtot[:,:,nkz]=img[:,:]   
-
-                    
-            if tag3==1 and tagRNSAT==1:  #horrible verrue
-                for iutloop in range(iblocRNSAT) :
-                    jbase=iutloop%w
-                    ibase=int(iutloop/w)
-                    jspe=(iutrnsat%nk)*iblocRNSAT
-                    ispe=int(iutrnsat/nk)
-                    imgtotstep[ispe+jspe]=img[ibase,jbase]
-                    iutrnsat=iutrnsat+1
- 
-
-        if tagRNSAT==1:  # je ne m'en suis pas sorti avec les reshape
-            ispe=w*h
-            for iut in range(nk):
-                isk=iut*iblocRNSAT
-                for jut in range(h) :
-                    imgtot[jut,:,iut]=imgtotstep[jut*w+isk:(jut+1)*w+isk]
-               
-        if tag3==0 or tag3==2:
-            return img,w,h,nk,nktemps
-        else:
-            print('retour tableau 3-D (%dx%dx%d)'%(w,h,nk))
-            return imgtot,w,h,nk,nktemps
-#
-def dat2mat(imgName):
-#
-# on rajoute un test dur l'existence mÃªme du fichier
-#
-
-    print('dat2mat : version Janvier 2018')
-
-    try :
-        fin=open(imgName,'rb');
-    except IOError:
-        legx=imgName+': est un fichier non ouvrable'
-        print(legx)
-        print(u'Echec Ã  l\'appel de dat2mat')
-        return 0,0,0,0
-     
-
-    firm = fin.read(4)  
-    nlig = struct.unpack("h",fin.read(2))[0]
-    ncol = struct.unpack("h",fin.read(2))[0]
-
-#    
-    firm=np.zeros(8*ncol, dtype=np.byte() ) 
-    imgcxs = np.empty([nlig-1, ncol], dtype=np.complex64())
-    
-# on elimine la premiÃ¨re ligne : cf la doc !!
-    firm = fin.read(8*ncol)
-    
-    for iut in range(nlig-1):
-        firm = fin.read(8*ncol)
-        imgligne =  np.ndarray( 2*ncol, 'f', firm)
-        imgcxs[iut,:]=imgligne[0:2*ncol:2]+1j*imgligne[1:2*ncol:2]
-
-
-    return imgcxs, ncol, nlig-1, 1, 1
-#
-#######################################################################################
-#######################################################################################
-    #  FIN DES LECTURES
-#######################################################################################
-#######################################################################################
-#  OUTILS VISU
-#######################################################################################
-#######################################################################################
-#
-def visusarbase(tabima,zparam,tagspe):
-#
-# on commence par eliminer les tableaux manifestement trop petits...
-# ainsi que les tableaux reduit a la valeur 0 (entiere)
-#
-    if isinstance(tabima,int)==True:
-        legspe='Pas de visualisation : Tableau nul'
-        print(legspe)
-        return
-        
-    RSI=tabima.size
-    if RSI<16:
-        legspe='Pas de visualisation : Tableau manifestement beaucoup trop petit (%d) pour etre une image : pas d''affichage' %(RSI)
-        print(legspe)
-        return 0
-        
-    kparam=3
-    malegende='Essai de titre'
-    if zparam!=-999:
-        kparam=zparam
-#    else:
-#        print("visusar sans second parametre'
-        
-    R=tabima.shape
-    ZZ=len(R)
-    if ZZ==3:  # on traite Ã  part les couleurs  
-        malegende='3 canaux couleurs (RVB)'
-        print("Affichage comme image en couleur (3 canaux)")       
-        if tagspe > 0 :
-            plt.figure()
-        plt.imshow(tabima)       
-        if tagspe > 0 :
-            plt.show()
-        return 0
-        
-        
-
-    if np.isrealobj(tabima)==True:
-        print("Affichage d'une image reelle")
-        BB=tabima
-    if np.isrealobj(tabima)==False:
-        print("Affichage d'une image complexe : on prend le module")
-        BB=abs(tabima)
-    valmin=np.min(BB)
-    valmax=np.max(BB)
-    valsig=np.std(BB)
-    valmoy=np.mean(BB)
-    legx='Min %.3f   Max %.3f    Moy %.3f   Ect %.3f ' %(valmin,valmax,valmoy,valsig)
- 
-    
-    
-    if kparam>0:
-        valseuil=valmoy+kparam*valsig
-        malegende='Image seuillee : valmoy + %.3f sigma  (%.2f)' %(kparam,valseuil)
-        if valseuil>valmax:
-            valseuil=valmax
-            malegende='Image sans seuillage'
-        masque=BB<valseuil
-        BB=BB*masque+(1-masque)*valseuil
-        
-    if kparam<0:
-        valseuil=-kparam
-        malegende='Image seuillee : %.2f' %(valseuil)
-        masque=BB<valseuil
-        BB=BB*masque+(1-masque)*valseuil
-        
-    if kparam==0:
-        malegende='Image sans seuillage'
-              
-    if tagspe <0 :
-        print('Visusar sans affichage')
-        print(legx)
-        return(BB)              
-              
-    if tagspe > -1 :
-        if tagspe>0 :
-            plt.figure()
-            plt.xlabel(legx)
-        plt.imshow(BB)   
-        plt.gray()        
- 
-    if kparam>0: 
-        print('Image seuillee : valmoy + %.3f sigma  (%.2f)' %(kparam,valseuil) )
-    else :
-        print('Image non seuillee : entre %.3f et %.3f ' %(valmin, valmax))
-    
-    if tagspe>0:              
-        plt.title(malegende)    
-        ncol=np.size(tabima,1)
-        nlig=np.size(tabima,0)
-        print('plt.show dans visusar : image %d x %d'%(ncol,nlig))
-        plt.show()
-        
-    return BB
-    
-    
-def visusar(tabima,zparam=-999):
-    """
-    affichage d'images plutot radar.  Si image complexe : affichage de la valeur absolue
-    
-    plt.show() incorporÃ© dans cette routine
-    
-    Arguments en entrÃ©e : 1 ou 2
-    
-        argument 1 : tableau 2D image
-        
-        argument 2 (facultatif) : facteur de la formule <<valeur moyenne + fak * Ã©cart type >>
-        Si ce facteur est nul, l'image ne sera pas seuillÃ©e
-        
-    Argument en sortie : le tableau affichÃ© (avec seuillage)
-        
-    Utilisez visusarZ (mÃªme syntaxe) pour Ã©viter le plt.show()     
-    
-    Utilisez visusarW (mÃªme syntaxe) pour n'avoir aucun affichage 
-    """
-    return visusarbase(tabima,zparam,1) 
-    
-def visusarZ(tabima, *therest):
-    nnn=-999
-    if(len(therest)==1):
-        nnn=therest[0]
-    return visusarbase(tabima,nnn,0) 
-    
-    
-def visusarW(tabima, *therest):
-    nnn=-999
-    if(len(therest)==1):
-        nnn=therest[0]
-    return visusarbase(tabima,nnn,-1)
-    
-####################################################################
-    # fin outils visu
-####################################################################
-     
-
-#######################################
-def dimimabase(imgName):
-    
-    if imgName.endswith('.dim') :
-        w, h, nk, nktemps  = _readDimZ(imgName)
-    else :       
-        imgName = os.path.splitext(imgName)[0]
-        w, h, nk, nktemps  = _readDimZ(imgName + '.dim')
-#    print('n colonnes '+'%d'%w+'  nlignes '+'%d'%h+' ncanaux '+'%d'%nk)
-    return w, h, nk, nktemps    
-    
-    
-#######################################
-def dimimadim(imgNameParam):
-    
-    if imgNameParam.endswith('.dim') :
-        return(_readDimparamZV2(imgNameParam))
-        
-    else :
-        return( 0, 0, 0 )
-        w, h, nk, nktemps, imgName  = _readDimZ(imgNameParam)
-      
-    
-    #
-#######################################
-    # compatibilitÃ© ascendante
-#
-def dimima(imgName):
-    return(dimimabase(imgName))
-#
-#
-######################################################
-######################################################
-# SEPTEMBRE 2017
-#
-
-typecode='<f'  # < : little endian
-hdrcode='byte order = 0'  # pour .hdr d'IDL/ENVI
-imacode='-byteorder = 0'  # pour .dim (mesure conservatoire)
-
-
-def mat2imz( tabimage, nomimage, *therest):
-    
-    """
-    Procedure pour ecrire un tableau dans un fichier au format TelecomParisTech
-    Le tableau sera archivÃ© en :
-        .ima si tableau 8 bits
-        .IMF sinon
-        .CXF si complexe   
-    Si le tableau est Ã  3 dimensions (pile TIVOLI), l'archivage se fera en .IMA
-    Exemple d'appel :
-    mat2imz( montableau2d, 'MaSortie')
-    Pour avoir aussi  le fichier .hdr d'IDL
-    mat2imz( montableau2d, 'MaSortie', 'idl')
-    """
-
-
-
-
-    nomdim=nomimage+'.dim'
-    
-    taghdr=0
-    testchar=0
-       
-    if(len(therest)==1):
-        if therest[0]=="idl" :
-            taghdr=1
-    
-    ndim=np.ndim(tabimage)
-    if(ndim<2):
-        print('mat2imz demande un tableau 2D ou 3D')
-        return
-    if(ndim>3):
-        print('mat2imz demande un tableau 2D ou 3D')
-        return
-
- 
-    nlig=np.size(tabimage,0)
-    ncol=np.size(tabimage,1)
-    nplan=1  # par defaut.. pour idl
-#
-# Cas image 2D
-#      
-    if ndim==2:      
-        fp=open(nomdim,'w')
-        fp.write('%d'%ncol+'  %d'%nlig)
-        fp.close()        
-        imode=np.iscomplex(tabimage[0][0])
-        if imode==True :
-            nomimagetot=nomimage+'.CXF'
-            fp=open(nomimagetot,'wb')
-            for iut in range(nlig):
-                for jut in range(ncol):
-                    fbuff=float(tabimage.real[iut][jut])
-                    record=struct.pack( typecode, fbuff)
-                    fp.write(record)
-                    fbuff=float(tabimage.imag[iut][jut])
-                    record=struct.pack( typecode, fbuff)
-                    fp.write(record)
-            fp.close()
-            
-        else :
-            mintab=np.min(tabimage)
-            maxtab=np.max(tabimage)
-            if(mintab>-0.0001 ) :
-                if (maxtab<255.0001):
-                    testchar=1
-                    nomimagetot=nomimage+'.ima'
-                    ucima=np.uint8(tabimage)
-                    fp=open(nomimagetot,'wb')
-                    for iut in range(nlig):
-                        for jut in range(ncol):
-                            record=struct.pack( 'B', ucima[iut][jut])
-                            fp.write(record)
-                    
-                else :
-                    nomimagetot=nomimage+'.IMF'
-                    fp=open(nomimagetot,'wb')
-                    for iut in range(nlig):
-                        for jut in range(ncol):
-                            fbuff=float(tabimage[iut][jut])
-                            record=struct.pack( typecode, fbuff)
-                            fp.write(record)
-            fp.close()
-                
-        
-        
-        
-               
-    if ndim==3:    
-        nplan=np.size(tabimage,2)       
-        imode=np.iscomplex(tabimage[0][0][0])
-        mintab=np.min(tabimage)
-        maxtab=np.max(tabimage)
-        if(mintab>-0.0001 ) :
-            if (maxtab<255.0001):
-                testchar=1
-        
-        
-        fp=open(nomdim,'w')      
-        fp.write('%d'%ncol+'  %d'%nlig+'  %d'%nplan+'   1'+'\n')
-        if imode==True :
-            fp.write('-type CFLOAT')
-        else :
-            if testchar==0:
-                fp.write('-type FLOAT')
-            if testchar==1:
-                fp.write('-type U8')
-        fp.close()
-        
-        nomimagetot=nomimage+'.IMA'
-        fp=open(nomimagetot,'wb')
-        if imode==True :
-            for lut in range(nplan):
-                for iut in range(nlig):
-                    for jut in range(ncol):
-                        fbuff=float(tabimage.real[iut][jut][lut])
-                        record=struct.pack( typecode, fbuff)
-                        fp.write(record)
-                        fbuff=float(tabimage.imag[iut][jut][lut])
-                        record=struct.pack( typecode, fbuff)
-                        fp.write(record)
-        else :
-            if(testchar==1) :
-                for lut in range(nplan):
-                    ucima=np.uint8(tabimage[:,:,lut])
-                    for iut in range(nlig):
-                        for jut in range(ncol):
-                            record=struct.pack( 'B', ucima[iut][jut])
-                            fp.write(record)
-            else :
-                for lut in range(nplan):
-                    for iut in range(nlig):
-                        for jut in range(ncol):
-                            fbuff=float(tabimage[iut][jut][lut])
-                            record=struct.pack( typecode, fbuff)
-                            fp.write(record)
-                            
-                            
-        fp.close()
-                
-    if taghdr==1 :
-        noffset=0
-        nomhdr=nomimagetot+'.hdr'
-        fp=open(nomhdr,'w')   
-        fp.write('ENVI \n')         
-        fp.write('{Fichier produit par tiilab.mat2imz (python) } \n')
-        fp.write('samples = %d'%ncol+'\n')
-        fp.write('lines = %d'%nlig+'\n')
-        fp.write('bands = %d'%nplan+'\n')
-        fp.write('header offset = %d'%noffset+'\n')
-        fp.write('file type = ENVI Standard \n')
-        if imode==True :
-            fp.write('data type = 6 \n')
-        else :
-            if(testchar==1) : 
-                fp.write('data type = 1  \n')
-            else : 
-                fp.write('data type = 4  \n')
-            
-        fp.write('interleave = bsq \n')
-        fp.write(hdrcode+'\n')
-        if imode==True :
-            fp.write('complex function = Magnitude  \n')
-        
-        
-        fp.close()
-        
-        
-        
-        
-######################################################
-######################################################
-######################################################
-
-    
-    
\ No newline at end of file
diff --git a/requirements_dev.txt b/requirements_dev.txt
index 5e164dd..9194494 100644
--- a/requirements_dev.txt
+++ b/requirements_dev.txt
@@ -7,6 +7,6 @@ tox==3.14.0
 coverage==4.5.4
 Sphinx==1.8.5
 twine==1.14.0
-
+Click==7.1.2
 
 
diff --git a/setup.cfg b/setup.cfg
index 4017ef9..edba556 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -7,7 +7,7 @@ tag = True
 search = version='{current_version}'
 replace = version='{new_version}'
 
-[bumpversion:file:merlin/__init__.py]
+[bumpversion:file:deepdespeckling/__init__.py]
 search = __version__ = '{current_version}'
 replace = __version__ = '{new_version}'
 
diff --git a/setup.py b/setup.py
index 912f2c5..d5392be 100644
--- a/setup.py
+++ b/setup.py
@@ -4,18 +4,17 @@
 
 from setuptools import setup, find_packages
 
-with open('README.rst') as readme_file:
+with open('README.md') as readme_file:
     readme = readme_file.read()
 
 with open('HISTORY.rst') as history_file:
     history = history_file.read()
 
-requirements = [ ]
-
+requirements = ['Click>=7.0', ]
 test_requirements = [ ]
 
 setup(
-    author=["youcef kemiche","pierre blanchard","emanuele dalsasso"],
+    author="emanuele dalsasso, youcef kemiche, pierre blanchard",
     author_email='y.kemiche06@hotmail.com',
     python_requires='>=3.6',
     classifiers=[
@@ -31,19 +30,19 @@
     description="Python Boilerplate contains all the boilerplate you need to create a Python package.",
     entry_points={
         'console_scripts': [
-            'merlin=merlin.cli:main',
+            'deepdespeckling=deepdespeckling.cli:main',
         ],
     },
-    install_requires=requirements,
+    install_requires=["numpy","Pillow","scipy","torch","opencv-python","tqdm"],
     license="MIT license",
     long_description=readme + '\n\n' + history,
     include_package_data=True,
-    keywords='merlin',
-    name='merlin',
-    packages=find_packages(include=['merlin', 'merlin.*']),
+    keywords='deepdespeckling',
+    name='deepdespeckling',
+    packages=find_packages(include=['deepdespeckling', 'deepdespeckling.*']),
     test_suite='tests',
     tests_require=test_requirements,
-    url='https://github.com/audreyr/merlin',
+    url='https://github.com/ykemiche/deepdespeckling',
     version='0.1.0',
     zip_safe=False,
 )
diff --git a/tox.ini b/tox.ini
index 3143afd..91d6dbd 100644
--- a/tox.ini
+++ b/tox.ini
@@ -10,7 +10,7 @@ python =
 [testenv:flake8]
 basepython = python
 deps = flake8
-commands = flake8 merlin tests
+commands = flake8 deepdespeckling tests
 
 [testenv]
 setenv =


Test patch:
diff --git a/deepdespeckling/merlin/test/__init__.py b/deepdespeckling/merlin/test/__init__.py
new file mode 100644
index 0000000..965e8dd
--- /dev/null
+++ b/deepdespeckling/merlin/test/__init__.py
@@ -0,0 +1,11 @@
+"""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/merlinsar/test/load_cosar.py b/deepdespeckling/merlin/test/load_cosar.py
similarity index 100%
rename from merlinsar/test/load_cosar.py
rename to deepdespeckling/merlin/test/load_cosar.py
diff --git a/deepdespeckling/merlin/test/model.py b/deepdespeckling/merlin/test/model.py
new file mode 100644
index 0000000..e96989f
--- /dev/null
+++ b/deepdespeckling/merlin/test/model.py
@@ -0,0 +1,154 @@
+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
+
+
+
+
+class Model(torch.nn.Module):
+
+    def __init__(self,device):
+        super().__init__()
+
+        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):
+        """  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/deepdespeckling/merlin/test/model_test.py b/deepdespeckling/merlin/test/model_test.py
new file mode 100644
index 0000000..43305c1
--- /dev/null
+++ b/deepdespeckling/merlin/test/model_test.py
@@ -0,0 +1,163 @@
+from deepdespeckling.merlin.test.utils import *
+from deepdespeckling.merlin.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):
+
+        """ 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))
+
+            # 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/merlinsar/test/results/denoised_imag_test_image_data.png b/deepdespeckling/merlin/test/results/denoised_imag_test_image_data.png
similarity index 100%
rename from merlinsar/test/results/denoised_imag_test_image_data.png
rename to deepdespeckling/merlin/test/results/denoised_imag_test_image_data.png
diff --git a/deepdespeckling/merlin/test/saved_model/model.pth b/deepdespeckling/merlin/test/saved_model/model.pth
new file mode 100644
index 0000000..827285b
Binary files /dev/null and b/deepdespeckling/merlin/test/saved_model/model.pth differ
diff --git a/deepdespeckling/merlin/test/spotlight.py b/deepdespeckling/merlin/test/spotlight.py
new file mode 100644
index 0000000..71c86c2
--- /dev/null
+++ b/deepdespeckling/merlin/test/spotlight.py
@@ -0,0 +1,179 @@
+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 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(image_path,destination_directory,stride_size=64,
+                model_weights_path= os.path.join(this_dir, "saved_model", "model.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)
+
+    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)
+        
+
+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):
+    """ 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)
+
+
+
+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,
+                        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')
+        crop_fixed(image_png, image_data_real, image_data_imag, destination_directory, test_data)
+    else:
+        print('FREE')
+        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)
+
+    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)
\ No newline at end of file
diff --git a/deepdespeckling/merlin/test/utils.py b/deepdespeckling/merlin/test/utils.py
new file mode 100644
index 0000000..0447558
--- /dev/null
+++ b/deepdespeckling/merlin/test/utils.py
@@ -0,0 +1,558 @@
+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_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:
+            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
+            print('before permutation', x_start, y_start, x_end, y_end)
+            # case crop is done bottom right - top left : WORKS
+            if x_start > x_end and y_start > y_end:
+                print('Case n2 from bottom right to top left')
+                tempxstart = x_start
+                tempystart = y_start
+
+                x_start = tempxstart - 32
+                x_end = tempxstart
+
+                y_start = tempystart - 32
+                y_end = tempystart
+                print('after permutation', x_start, y_start, x_end, y_end)
+
+            elif x_start > x_end and y_start < y_end:
+                print('Case n3 top right to botton left')
+                tempxstart = x_start
+                tempystart = y_start
+
+                x_start = tempxstart - 32
+                y_start = tempystart
+
+                x_end = tempxstart
+                y_end = tempystart + 32
+                print('after permutation', x_start, y_start, x_end, y_end)
+
+            elif x_start < x_end and y_start > y_end:
+                print('Case n4 bottom left to top right')
+                tempxstart = x_start
+                tempystart = y_start
+
+                x_start = tempxstart
+                y_start = tempystart - 32
+                x_end = tempxstart + 32
+                y_end = tempystart
+                print('after permutation', x_start, y_start, x_end, y_end)
+
+            else:
+                print('general case')
+                x_end = x_start + 32
+                y_end = y_start + 32
+                print('after permutation', x_start, y_start, x_end, y_end)
+
+            refPoint = [(x_start, y_start), (x_end, y_end)]
+            print('refPoint', refPoint)
+            ## 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
+
+        """
+    # 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
+            print('before permutation', x_start, y_start, x_end, y_end)
+            # case crop is done bottom right - top left : WORKS
+            if x_start > x_end and y_start > y_end:
+                print('Case n2 from bottom right to top left')
+                tempx = x_start
+                x_start = x_end
+                x_end = tempx
+
+                tempy = y_start
+                y_start = y_end
+                y_end = tempy
+                print('after permutation', x_start, y_start, x_end, y_end)
+
+            elif x_start > x_end and y_start < y_end:
+                print('Case n3 top right to botton left')
+                tempxstart = x_start
+                tempystart = y_start
+                tempxend = x_end
+                tempyend = y_end
+
+                x_start = tempxend
+                y_start = tempystart
+                x_end = tempxstart
+                y_end = tempyend
+                print('after permutation', x_start, y_start, x_end, y_end)
+
+            elif x_start < x_end and y_start > y_end:
+                print('Case n4 bottom left to top right')
+                tempxstart = x_start
+                tempystart = y_start
+                tempxend = x_end
+                tempyend = y_end
+
+                x_start = tempxstart
+                y_start = tempyend
+                x_end = tempxend
+                y_end = tempystart
+                print('after permutation', x_start, y_start, x_end, y_end)
+
+            else:
+                print('after permutation', x_start, y_start, x_end, y_end)
+
+            ## 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 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
diff --git a/deepdespeckling/sar2sar/test/__init__.py b/deepdespeckling/sar2sar/test/__init__.py
new file mode 100644
index 0000000..cc2be44
--- /dev/null
+++ b/deepdespeckling/sar2sar/test/__init__.py
@@ -0,0 +1,11 @@
+"""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
new file mode 100644
index 0000000..f3da7d9
--- /dev/null
+++ b/deepdespeckling/sar2sar/test/load_cosar.py
@@ -0,0 +1,46 @@
+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/merlinsar/test/model.py b/deepdespeckling/sar2sar/test/model.py
similarity index 74%
rename from merlinsar/test/model.py
rename to deepdespeckling/sar2sar/test/model.py
index 9acd67f..6cda078 100644
--- a/merlinsar/test/model.py
+++ b/deepdespeckling/sar2sar/test/model.py
@@ -18,67 +18,19 @@
 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.
+class Model(torch.nn.Module):
 
-            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 AE(torch.nn.Module):
-
-    def __init__(self,batch_size,eval_batch_size,device):
+    def __init__(self,height,width,device):
         super().__init__()
 
-        self.batch_size=batch_size
-        self.eval_batch_size=eval_batch_size
         self.device=device
 
-        self.x = None
-        self.height = None
-        self.width = None
-        self.out_channels = None
-        self.kernel_size_cv2d = None
-        self.stride_cv2d = None
-        self.padding_cv2d = None
-        self.kernel_size_mp2d = None
-        self.stride_mp2d = None
-        self.padding_mp2d = None
-        self.alpha = None
+        self.height = height
+        self.width = width
+
+
         self.pool = torch.nn.MaxPool2d(kernel_size=2, stride=2)
         self.leaky = torch.nn.LeakyReLU(0.1)
 
@@ -123,7 +75,7 @@ def __init__(self,batch_size,eval_batch_size,device):
         self.upscale2d = torch.nn.UpsamplingNearest2d(scale_factor=2)
 
 
-    def forward(self,x ,batch_size):
+    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
@@ -143,7 +95,7 @@ def forward(self,x ,batch_size):
         a numpy array containing the denoised image i.e the image itself minus the noise
 
         """
-        x=torch.reshape(x, [batch_size, 1, 256, 256])
+        x=torch.reshape(x, [1, 1, self.height, self.width])
         skips = [x]
 
         n = x
diff --git a/merlinsar/test/model_test.py b/deepdespeckling/sar2sar/test/model_test.py
similarity index 79%
rename from merlinsar/test/model_test.py
rename to deepdespeckling/sar2sar/test/model_test.py
index 42849e2..6324592 100644
--- a/merlinsar/test/model_test.py
+++ b/deepdespeckling/sar2sar/test/model_test.py
@@ -2,6 +2,7 @@
 from merlinsar.test.model import *
 import torch
 import numpy as np
+from tqdm import tqdm 
 
 M = 10.089038980848645
 m = -1.429329123112601
@@ -46,9 +47,7 @@ def load(self,model,weights_path):
         return model
 
      
-
-
-    def test(self,model, test_files, weights_path, save_dir, dataset_dir, stride):
+    def test(self,test_files, weights_path, save_dir,stride,patch_size,height,width):
 
         """ Description
                     ----------
@@ -73,7 +72,7 @@ def test(self,model, test_files, weights_path, save_dir, dataset_dir, stride):
 
         assert len(test_files) != 0, 'No testing data!'
 
-        loaded_model = AE(12,1,torch.device("cuda:0" if torch.cuda.is_available() else "cpu"))
+        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)
@@ -89,10 +88,7 @@ def test(self,model, test_files, weights_path, save_dir, dataset_dir, stride):
                                                            real_image.shape[2], 1)
 
 
-            # scan on image dimensions
-            stride = 64
-            pat_size = 256
-
+    
             # Pad the image
             im_h = np.size(real_image, 1)
             im_w = np.size(real_image, 2)
@@ -101,24 +97,24 @@ def test(self,model, test_files, weights_path, save_dir, dataset_dir, stride):
             output_clean_image_1 = np.zeros(i_real_part.shape)
             output_clean_image_2 = np.zeros(i_real_part.shape)
 
-            if im_h == pat_size:
+            if im_h == patch_size:
                 x_range = list(np.array([0]))
 
             else:
-                x_range = list(range(0, im_h - pat_size, stride))
-                if (x_range[-1] + pat_size) < im_h: x_range.extend(range(im_h - pat_size, im_h - pat_size + 1))
+                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 == pat_size:
+            if im_w == patch_size:
                 y_range = list(np.array([0]))
 
             else:
-                y_range = list(range(0, im_w - pat_size, stride))
-                if (y_range[-1] + pat_size) < im_w: y_range.extend(range(im_w - pat_size, im_w - pat_size + 1))
+                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 x_range:
+            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 + pat_size, y:y + pat_size, :],i_imag_part[:, x:x + pat_size, y:y + pat_size, :])
+                    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)   
@@ -130,23 +126,23 @@ def test(self,model, test_files, weights_path, save_dir, dataset_dir, stride):
                     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,1).detach().numpy()                          
+                    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 + pat_size, y:y + pat_size, :] = output_clean_image_1[:, x:x + pat_size,
-                                                                                 y:y + pat_size,
+                    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,1).detach().numpy()
+                    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 + pat_size, y:y + pat_size, :] = output_clean_image_2[:, x:x + pat_size,
-                                                                                 y:y + pat_size,
+                    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 + pat_size, y:y + pat_size, :] = count_image[:, x:x + pat_size, y:y + pat_size,
-                                                                        :] + np.ones((1, pat_size, pat_size, 1))
+                    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
diff --git a/deepdespeckling/sar2sar/test/results/denoised_imag_test_image_data.png b/deepdespeckling/sar2sar/test/results/denoised_imag_test_image_data.png
new file mode 100644
index 0000000..8aa904a
Binary files /dev/null and b/deepdespeckling/sar2sar/test/results/denoised_imag_test_image_data.png differ
diff --git a/deepdespeckling/sar2sar/test/saved_model/model.pth b/deepdespeckling/sar2sar/test/saved_model/model.pth
new file mode 100644
index 0000000..827285b
Binary files /dev/null and b/deepdespeckling/sar2sar/test/saved_model/model.pth differ
diff --git a/merlinsar/test/main.py b/deepdespeckling/sar2sar/test/spotlight.py
similarity index 57%
rename from merlinsar/test/main.py
rename to deepdespeckling/sar2sar/test/spotlight.py
index cc67eec..226f28f 100644
--- a/merlinsar/test/main.py
+++ b/deepdespeckling/sar2sar/test/spotlight.py
@@ -3,7 +3,6 @@
 from merlinsar.test.model import *
 from merlinsar.test.utils import *
 from merlinsar.test.model_test import * 
-import argparse
 import os
 from glob import glob
 
@@ -13,10 +12,12 @@
 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="merlin-sar/merlin/test/saved_model/model.pth"):
+                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
@@ -29,16 +30,21 @@ def despeckle(image_path,destination_directory,stride_size=64,
             ----------
 
     """
-    model = AE(12,1,torch.device("cuda:0" if torch.cuda.is_available() else "cpu"))
+
     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[1200:1200+256,:256,:])
+    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" % (
@@ -46,12 +52,59 @@ def despeckle(image_path,destination_directory,stride_size=64,
             
     test_files = glob((test_data + '/*.npy'))
     print(test_files)
-    denoiser.test(model,test_files,model_weights_path, save_dir=destination_directory, dataset_dir=destination_directory,
-                  stride=stride_size)
+
+    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="merlin-sar/merlin/test/saved_model/model.pth"):
+                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
@@ -64,7 +117,7 @@ def despeckle_from_crop(image_path,destination_directory,stride_size=64,
             ----------
 
     """
-    model = AE(12,1,torch.device("cuda:0" if torch.cuda.is_available() else "cpu"))
+
     denoiser=Denoiser()
 
     if not os.path.exists(destination_directory+'\\processed_image'):
@@ -72,6 +125,11 @@ def despeckle_from_crop(image_path,destination_directory,stride_size=64,
 
     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)
 
@@ -97,5 +155,5 @@ def despeckle_from_crop(image_path,destination_directory,stride_size=64,
 
     test_files = glob((test_data +'/test_image_data_cropped.npy'))
     print(test_files)
-    denoiser.test(model,test_files,model_weights_path, save_dir=destination_directory, dataset_dir=destination_directory,
-                  stride=stride_size)
+    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/merlinsar/test/utils.py b/deepdespeckling/sar2sar/test/utils.py
similarity index 98%
rename from merlinsar/test/utils.py
rename to deepdespeckling/sar2sar/test/utils.py
index 0efeab6..8e16f73 100644
--- a/merlinsar/test/utils.py
+++ b/deepdespeckling/sar2sar/test/utils.py
@@ -1,11 +1,9 @@
 import numpy as np
 from PIL import Image
-import scipy.ndimage
 from scipy import special
 from scipy import signal
 from merlinsar.test.load_cosar import cos2mat
 import cv2
-import PIL
 from PIL import Image
 import numpy as np
 from numpy import asarray
@@ -240,12 +238,6 @@ def save_real_imag_images_noisy(real_part, imag_part, imagename, save_dir):
     np.save(imagfilename, imag_part)
     store_data_and_plot(np.sqrt(2) * np.abs(imag_part), threshold, imagfilename)
 
-
-def save_residual(filepath, residual):
-    residual_image = np.squeeze(residual);
-    plt.imsave(filepath, residual_image)
-
-
 def cal_psnr(Shat, S):
     # takes amplitudes in input
     # Shat: a SAR amplitude image
@@ -255,7 +247,6 @@ def cal_psnr(Shat, S):
     return res
 
 def crop(image_png,image_data_real, image_data_imag,destination_directory,test_data, cropping):
-    print('cropping in  crop', cropping)
     # HERE I READ THE PNG FILE
     oriImage = image_png.copy()
     cropping = False
diff --git a/img/coordinates/denoised_test_image_data.png b/img/coordinates/denoised_test_image_data.png
new file mode 100644
index 0000000..e52aaf3
Binary files /dev/null and b/img/coordinates/denoised_test_image_data.png differ
diff --git a/img/coordinates/noisy_test_image_data.png b/img/coordinates/noisy_test_image_data.png
new file mode 100644
index 0000000..8010333
Binary files /dev/null and b/img/coordinates/noisy_test_image_data.png differ
diff --git a/img/crop/denoised_test_image_data.png b/img/crop/denoised_test_image_data.png
new file mode 100644
index 0000000..ac4f223
Binary files /dev/null and b/img/crop/denoised_test_image_data.png differ
diff --git a/img/crop/noisy_test_image_data.png b/img/crop/noisy_test_image_data.png
new file mode 100644
index 0000000..cf9c7ff
Binary files /dev/null and b/img/crop/noisy_test_image_data.png differ
diff --git a/merlinsar/test/__init__.py b/merlinsar/test/__init__.py
deleted file mode 100644
index 283db45..0000000
--- a/merlinsar/test/__init__.py
+++ /dev/null
@@ -1,5 +0,0 @@
-"""Top-level package for merlin."""
-
-__author__ = """youcef kemiche"""
-__email__ = 'y.kemiche06@hotmail.com'
-__version__ = '0.1.0'
diff --git a/merlinsar/test/saved_model/model.pth b/merlinsar/test/saved_model/model.pth
deleted file mode 100644
index 4e8e3a2..0000000
Binary files a/merlinsar/test/saved_model/model.pth and /dev/null differ
diff --git a/tests/merlin/test/test_utils.py b/tests/merlin/test/test_utils.py
new file mode 100644
index 0000000..14eff69
--- /dev/null
+++ b/tests/merlin/test/test_utils.py
@@ -0,0 +1,450 @@
+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/merlin/train/test_model.py b/tests/merlin/train/test_model.py
new file mode 100644
index 0000000..7ea55fe
--- /dev/null
+++ b/tests/merlin/train/test_model.py
@@ -0,0 +1,81 @@
+#!/usr/bin/env python
+"""Tests for `merlin` package."""
+
+import sys
+sys.path.insert(0, 'C:/Users/ykemiche/OneDrive - Capgemini/Desktop/deepdespeckling/deepdespeckling')
+
+from deepdespeckling.merlin.train.model import *
+import numpy as np
+import torch 
+
+
+batch_size=1
+val_batch_size=1
+device="cuda:0" if torch.cuda.is_available() else "cpu"
+
+model = Model(batch_size,val_batch_size,device)
+x=np.random.rand(1,256,256,1)
+x=torch.tensor(x)
+x=x.type(torch.float32)
+
+y=np.random.rand(1,256,256,1)
+y=torch.tensor(y)
+y=y.type(torch.float32)
+
+out=model.forward(x,1)
+
+
+
+
+class TestNet():
+    """Test class for Net1 function"""
+
+    def test_net_instance_good_shape(self):
+        """Test that a Net1 instance has good shape
+        Parameters
+        ----------
+        capfd: fixture
+        Allows access to stdout/stderr output created
+        during test execution.
+        Returns
+        -------
+        None
+        """
+         
+        assert out.shape==torch.Size([1, 1, 256, 256])
+
+    def test_net_forward_returns_good_type(self, ):
+        """Tests that a net1 instance has good type
+        Returns
+        -------
+        None
+        """ 
+
+    assert type(out) == torch.Tensor, f"'x' should be a torch.Tensor, but is {type(x)}"
+
+    def test_loss_function_good_shape(self):
+        """Test that a Net1 instance has good shape
+        Parameters
+        ----------
+        capfd: fixture
+        Allows access to stdout/stderr output created
+        during test execution.
+        Returns
+        -------
+        None
+        """
+        loss=model.loss_function(out,y,1)
+        print(loss.shape)
+        assert type(loss) == torch.Tensor, f"'x' should be a torch.Tensor, but is {type(x)}"
+
+    def test_loss_function_returns_good_type(self, ):
+        """Tests that a net1 instance has good type
+        Returns
+        -------
+        None
+        """ 
+        loss=model.loss_function(out,y,1)
+
+        assert loss.shape == torch.Size([])
+
+ 
\ No newline at end of file
diff --git a/tests/sar2sar/test/__init__.py b/tests/sar2sar/test/__init__.py
new file mode 100644
index 0000000..cc2be44
--- /dev/null
+++ b/tests/sar2sar/test/__init__.py
@@ -0,0 +1,11 @@
+"""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
new file mode 100644
index 0000000..f3da7d9
--- /dev/null
+++ b/tests/sar2sar/test/load_cosar.py
@@ -0,0 +1,46 @@
+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
new file mode 100644
index 0000000..6cda078
--- /dev/null
+++ b/tests/sar2sar/test/model.py
@@ -0,0 +1,154 @@
+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
new file mode 100644
index 0000000..6324592
--- /dev/null
+++ b/tests/sar2sar/test/model_test.py
@@ -0,0 +1,166 @@
+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
new file mode 100644
index 0000000..8aa904a
Binary files /dev/null and b/tests/sar2sar/test/results/denoised_imag_test_image_data.png differ
diff --git a/tests/sar2sar/test/saved_model/model.pth b/tests/sar2sar/test/saved_model/model.pth
new file mode 100644
index 0000000..827285b
Binary files /dev/null and b/tests/sar2sar/test/saved_model/model.pth differ
diff --git a/tests/sar2sar/test/spotlight.py b/tests/sar2sar/test/spotlight.py
new file mode 100644
index 0000000..226f28f
--- /dev/null
+++ b/tests/sar2sar/test/spotlight.py
@@ -0,0 +1,159 @@
+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
new file mode 100644
index 0000000..8e16f73
--- /dev/null
+++ b/tests/sar2sar/test/utils.py
@@ -0,0 +1,325 @@
+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
new file mode 100644
index 0000000..3fa4480
--- /dev/null
+++ b/tests/sar2sar/train/.directory
@@ -0,0 +1,4 @@
+[Dolphin]
+Timestamp=2022,4,5,17,1,21
+Version=4
+ViewMode=1
diff --git a/tests/sar2sar/train/Dataset.py b/tests/sar2sar/train/Dataset.py
new file mode 100644
index 0000000..c7148c5
--- /dev/null
+++ b/tests/sar2sar/train/Dataset.py
@@ -0,0 +1,53 @@
+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/tests/sar2sar/train/GenerateDataset.py b/tests/sar2sar/train/GenerateDataset.py
new file mode 100644
index 0000000..c605613
--- /dev/null
+++ b/tests/sar2sar/train/GenerateDataset.py
@@ -0,0 +1,165 @@
+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
new file mode 100644
index 0000000..7f787ab
--- /dev/null
+++ b/tests/sar2sar/train/__init__.py
@@ -0,0 +1,12 @@
+"""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
new file mode 100644
index 0000000..bd3025b
--- /dev/null
+++ b/tests/sar2sar/train/merlin.py
@@ -0,0 +1,150 @@
+#!/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
new file mode 100644
index 0000000..5f4c2bd
--- /dev/null
+++ b/tests/sar2sar/train/model.py
@@ -0,0 +1,306 @@
+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
new file mode 100644
index 0000000..7766b6b
Binary files /dev/null and b/tests/sar2sar/train/sample/denoised_lely0_epoch_1.png differ
diff --git a/tests/sar2sar/train/utils.py b/tests/sar2sar/train/utils.py
new file mode 100644
index 0000000..a9fdcb7
--- /dev/null
+++ b/tests/sar2sar/train/utils.py
@@ -0,0 +1,271 @@
+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/tests/test_merlin.py b/tests/test_merlin.py
deleted file mode 100644
index d30900a..0000000
--- a/tests/test_merlin.py
+++ /dev/null
@@ -1,21 +0,0 @@
-#!/usr/bin/env python
-
-"""Tests for `merlin` package."""
-
-
-import unittest
-
-from merlin import merlin
-
-
-class TestMerlin(unittest.TestCase):
-    """Tests for `merlin` package."""
-
-    def setUp(self):
-        """Set up test fixtures, if any."""
-
-    def tearDown(self):
-        """Tear down test fixtures, if any."""
-
-    def test_000_something(self):
-        """Test something."""