From 33ba8fa73b97d4a3bfa94b34cb259db7b142ae5a Mon Sep 17 00:00:00 2001
From: Commandre Benjamin <benjamin.commandre@irstea.fr>
Date: Wed, 14 Nov 2018 14:52:52 +0100
Subject: [PATCH] =?UTF-8?q?Optimisation,=20ajout=20de=20la=20cr=C3=A9ation?=
 =?UTF-8?q?=20du=20projet=20qgis?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 Roseliere.py                 |  33 ++---
 app/Archive.py               | 116 +++++++++------
 app/Constantes.py            |   7 +-
 app/Outils.py                |  11 +-
 app/Processing.py            | 273 ++++++++++++-----------------------
 app/Projet_qgis.py           | 180 +++++++++++++++++++++++
 app/Toolbox.py               | 149 -------------------
 app/style_classification.qml | 205 ++++++++++++++++++++++++++
 app/style_image.qml          |  46 ++++++
 config.ini                   |  15 +-
 10 files changed, 639 insertions(+), 396 deletions(-)
 create mode 100644 app/Projet_qgis.py
 delete mode 100644 app/Toolbox.py
 create mode 100644 app/style_classification.qml
 create mode 100644 app/style_image.qml

diff --git a/Roseliere.py b/Roseliere.py
index 3810dd0..ce5f1d8 100644
--- a/Roseliere.py
+++ b/Roseliere.py
@@ -38,6 +38,7 @@ from osgeo import ogr
 
 import configparser 
 from app.Processing import Processing
+from app.Projet_qgis import Projet_qgis
 import app.Constantes as Constantes
 import app.Outils as Outils
 
@@ -53,6 +54,10 @@ class Roseliere(Processing):
 		self.logger = Outils.Log("log", "Roseliere")
 					
 		self.get_variable()
+
+		sortie_qgis = "{0}/Herbier.qgs".format(self.output_file)
+
+		self.qgis = Projet_qgis(sortie_qgis, self.style_image, self.style_classification)
 						
 	def get_variable(self):		
 		"""
@@ -82,8 +87,11 @@ class Roseliere(Processing):
 		# Classification year by line edit
 		self.classif_year = "{0}".format(configfile["emprise"]["annee_debut"])
 		
-		# Study area shapefile path by line edit
+		# Study area shapefile path
 		self.path_area = "{0}".format(configfile["emprise"]["chemin"])
+
+		# Object area shapefile path
+		self.object_area = "{0}".format(configfile["zone_etude"]["chemin"])
 		
 		# Connexion username and password by line edit
 		self.user = "{0}".format(configfile["theia"]["identifiant"])
@@ -92,20 +100,13 @@ class Roseliere(Processing):
 
 		# Output shapefile field name by line edit and field type by combo box
 		self.output_file = "{0}".format(configfile["sortie"]["chemin"])
+
+		self.style_image 			= "{0}".format(configfile["style"]["image"])
+		self.style_classification 	= "{0}".format(configfile["style"]["classification"])
 	
 	def run(self):
 		"""
-			Function to launch the processing. This function take account :
-			
-			- The ``Multi-processing`` check box if the processing has launched with multi process.
-				By default, this is checked. It need a computer with minimum 12Go memory.
-			- Append a few system value with :func:`get_variable`.
-			- There are 3 principal check boxes :
-				- to get number download available images
-				- for downloading and processing on theia platform
-				- to compute optimal threshold.
-				- to compute slope raster
-				- for classification processing.
+			Function to launch the processing.
 		"""
 		
 		# Start the processus
@@ -115,10 +116,10 @@ class Roseliere(Processing):
 		self.i_download()
 			
 		# # function to launch the image processing	
-		# self.i_images_processing()
+		self.i_images_processing()
 
-		# # Classification processing 
-		# self.i_classifier()
+		self.qgis.run(self.path_folder_dpt)
+		self.qgis.close()
 		
 		# Initialize variable without to close and launch again the application
 		Processing.__init__(self)
@@ -129,8 +130,6 @@ class Roseliere(Processing):
 		nb_day_processing = int(time.strftime('%d', time.gmtime(endTime - startTime))) - 1
 		self.logger.info("That is {0} day(s) {1}".format(nb_day_processing, time.strftime('%Hh %Mmin%S', time.gmtime(endTime - startTime))))
 		
-
-		
 if __name__ == "__main__":
 	
 	myapp = Roseliere()		
diff --git a/app/Archive.py b/app/Archive.py
index 73ae949..e361ec1 100644
--- a/app/Archive.py
+++ b/app/Archive.py
@@ -17,12 +17,13 @@
 # You should have received a copy of the GNU General Public License
 # along with PHYMOBAT 3.0.  If not, see <http://www.gnu.org/licenses/>.
 
-import os, sys, glob, re, time, io
+import os, sys, glob, re, time, io, gc
 import math, subprocess, json
 import urllib.request
 import tarfile, zipfile
 import requests
 import configparser 
+import datetime
 
 from osgeo import ogr, gdal, gdal_array
 import numpy as np
@@ -33,6 +34,7 @@ from collections import defaultdict, UserDict
 import app.Constantes as Constantes
 import app.Satellites as Satellites
 import app.Outils as Outils
+from memory_profiler import profile
 
 class Archive():
     """
@@ -53,15 +55,25 @@ class Archive():
         :type repertory: str
     """
     
-    def __init__(self, captor, list_year, box, repertory, emprise):
+    def __init__(self, captor, start_year, emprise, repertory, study_area):
         """
             Create a new 'Archive' instance
         """
         self._captor = captor
-        self._list_year = list_year.split(";")
-        self._box = box
+
+        now = datetime.datetime.now()
+
+        self._list_year = []
+
+        for i in range(int(start_year), now.year) :
+             self._list_year.append(i)
+
+        self._list_year.append(now.year)
+        
+        self._box = emprise
         self._repertory = repertory
         self.emprise = emprise
+        self.study_area = study_area
         
         self.list_archive = []
 
@@ -209,15 +221,15 @@ class Archive():
         self.logger.info("Images availables")
         for year in self._list_year:
             
-            first_date = year.split(',')[0]
+            # first_date = year.split(',')[0]
             # Tricks to put whether a year or a date (year-month-day)
-            try:
-                end_date = year.split(',')[1]
-                self.logger.info("=============== {0} to {1} ===============".format(first_date, end_date))
-                self.url = "{0}/{1}/api/collections/{2}/search.json?lang=fr&_pretty=true&completionDate={3}&box={4}&maxRecord=500&startDate={5}".format(self.server, self.resto, self._captor, end_date, self.coord_box_dd(), first_date) 
-            except IndexError:
-                self.logger.info("=============== {0} ===============".format(first_date))
-                self.url =  "{0}/{1}/api/collections/{2}/search.json?lang=fr&_pretty=true&q={3}&box={4}&maxRecord=500".format(self.server, self.resto, self._captor, year, self.coord_box_dd())
+            # try:
+            #     end_date = year.split(',')[1]
+            #     self.logger.info("=============== {0} to {1} ===============".format(first_date, end_date))
+            #     self.url = "{0}/{1}/api/collections/{2}/search.json?lang=fr&_pretty=true&completionDate={3}&box={4}&maxRecord=500&startDate={5}".format(self.server, self.resto, self._captor, end_date, self.coord_box_dd(), first_date) 
+            # except IndexError:
+            self.logger.info("=============== {0} ===============".format(year))
+            self.url =  "{0}/{1}/api/collections/{2}/search.json?lang=fr&_pretty=true&q={3}&box={4}&maxRecord=500".format(self.server, self.resto, self._captor, year, self.coord_box_dd())
             
             # Initialisation variable for a next page 
             # There is a next page, next = 1
@@ -248,7 +260,8 @@ class Archive():
                         archive_download = url_archive.replace("/download", "") # Path to download
                         out_archive = "{0}/{1}.tgz".format(self._repertory, name_archive)# Name after download
                         # if len(self.list_archive) == 0 : self.list_archive.append([archive_download, out_archive, feature_id])
-                        self.list_archive.append([archive_download, out_archive, feature_id]) 
+                        if "SENTINEL2X" not in out_archive :
+                            self.list_archive.append([archive_download, out_archive, feature_id]) 
                     
                     # Verify if there is another page (next)
                     for link in data_Dict['properties']['links'] :
@@ -338,16 +351,19 @@ class Archive():
 
         with open(self.fichier_sauvegarde, 'w') as configfile:
             self.sauvegarde.write(configfile)
- 
+    
         for cle in sorted(dico_date):
             liste_content = []
 
             for img in dico_date[cle]:
+                self.logger.info("Requête pour l'archive : {0}".format(img[1].split("/")[-1]))
                 url = "{0}/{1}/collections/{2}/{3}/download/?issuerId=theia".format(self.server, self.resto, self._captor, img[2])
                 reponse = requests.get(url, headers=head, proxies=proxyDict)
-                        
+                           
                 liste_content.append(reponse.content)
+                del reponse
 
+            # sys.exit()
             self.pourc_cloud(cle, liste_content)
             del liste_content
 
@@ -361,7 +377,7 @@ class Archive():
 
     def calcul_aire(self, tuiles_image):
         
-        option_warp     = gdal.WarpOptions(format='Mem',resampleAlg="lanczos")    
+        option_warp     = gdal.WarpOptions(format='Mem', resampleAlg="lanczos")    
         mosaic_image    = gdal.Warp("", tuiles_image, options=option_warp)
 
         rows = mosaic_image.RasterYSize # Rows number
@@ -387,17 +403,28 @@ class Archive():
         mask_spec = np.isin(data, [-10000, math.isnan], invert=True)
 
         # This is the same opposite False where there is 0
-        mask_cloud = np.isin(data, 0) 
+        mask_cloud = np.isin(data, 0)
+
+        del data 
 
         # If True in cloud mask, it take spectral image else False
         cloud = np.choose(mask_cloud, (False, mask_spec))
 
+        del mask_cloud
+
         # Sum of True. True is cloud 
-        dist = np.sum(cloud) 
+        dist = np.sum(cloud)
+
+        del cloud
+        ennuagement = 1.0 - (float(dist)/(np.sum(mask_spec)) if np.sum(mask_spec) != 0 else 0) 
+
+        del dist
+        del mask_spec
 
         # Computer cloud's percentage with dist (sum of cloud) by sum of the image's extent
-        return mosaic_nuage, 1.0 - (float(dist)/(np.sum(mask_spec)) if np.sum(mask_spec) != 0 else 0) 
-        
+        # return mosaic_nuage, 1.0 - (float(dist)/(np.sum(mask_spec)) if np.sum(mask_spec) != 0 else 0) 
+        return None, ennuagement
+
     def ecriture_geotiff(self, dataset, chemin):
 
         gdal.AllRegister()
@@ -409,23 +436,16 @@ class Archive():
 
         for band in range(dataset.RasterCount) :
             outdata.GetRasterBand(band + 1).WriteArray(\
-                dataset.GetRasterBand(band + 1).ReadAsArray(0, 0, dataset.RasterXSize, dataset.RasterYSize).astype(np.float32),\
+                dataset.GetRasterBand(band + 1).ReadAsArray(0, 0, dataset.RasterXSize, dataset.RasterYSize).astype(np.float32)/10000.0,\
                 0,0)
             outdata.GetRasterBand(band + 1).FlushCache()
     
         outdata = None
 
-    def clip(self, image):
-        
-        option_clip         = gdal.WarpOptions(cropToCutline=True,\
-        cutlineDSName=self.emprise, outputType=gdal.GDT_Float32 , format='Mem')
-
-        return gdal.Warp("", image, options=option_clip)
-
     def pourc_cloud(self, date, liste_content):
 
-        self.logger.info("Date : {0}".format(date))
-        extent_img = ['_SRE_B2.tif', '_SRE_B3.tif', '_SRE_B4.tif', '_SRE_B8.tif']
+        self.logger.info("Date : {0} -> {1} image(s)".format(date, len(liste_content)))
+        extent_img = ['_FRE_B2.tif', '_FRE_B3.tif', '_FRE_B4.tif', '_FRE_B8.tif']
         extent_nuage = ['_CLM_R1.tif']
 
         tuiles_image = []
@@ -434,9 +454,9 @@ class Archive():
         options_vrt         = gdal.BuildVRTOptions(separate=True)
         options_translate   = gdal.TranslateOptions(format="Mem")
 
-        self.logger.info("Extraction des images, nombre d'archives : {0}".format(len(liste_content)))
+        self.logger.info("Extraction des images")
 
-        for content in liste_content :
+        for idx, content in enumerate(liste_content) :
 
             tzip = zipfile.ZipFile(io.BytesIO(content))
             
@@ -444,23 +464,31 @@ class Archive():
             zip_img = tzip.namelist()
 
             liste_bandes = []
-            for extension in extent_img :
+            liste_mem = []
+            for id_ext, extension in enumerate(extent_img) :
                 img = [f for f in zip_img if extension in f][0]
+
                 mmap_name = "/vsimem/"+uuid4().hex
+                liste_mem.append(mmap_name)
                 gdal.FileFromMemBuffer(mmap_name, tzip.read(img))
                 liste_bandes.append(gdal.Open(mmap_name))
 
-            vrt = gdal.BuildVRT("", liste_bandes, options= options_vrt)
-            tuiles_image.append(self.clip(gdal.Translate("", vrt, options=options_translate)))
+            vrt = gdal.BuildVRT("", liste_bandes, options=options_vrt)
+            tuiles_image.append(Outils.clip(gdal.Translate("", vrt, options=options_translate), self.emprise ))
+
+            for mmap_name in liste_mem :
+                gdal.Unlink(mmap_name)
 
-            del liste_bandes
+            liste_bandes = None
 
             image_nuage = [f for f in zip_img if '_CLM_R1.tif' in f][0]                
             mmap_name = "/vsimem/"+uuid4().hex
 
             gdal.FileFromMemBuffer(mmap_name, tzip.read(image_nuage))
-            tuiles_nuage.append(self.clip(gdal.Open(mmap_name)))
+            tuiles_nuage.append(Outils.clip(gdal.Open(mmap_name), self.study_area))
             gdal.Unlink(mmap_name)
+            del tzip
+            liste_content[idx] = None
 
         del liste_content
 
@@ -468,23 +496,25 @@ class Archive():
         image, aire = self.calcul_aire(tuiles_image)
         del tuiles_image
         
-        if aire > 0.0 :       
-            
+        if aire > 0.0 :        
             self.logger.info("Calcul de l'ennuagement")
             nuage, ennuagement = self.calcul_ennuagement(tuiles_nuage)
+            self.logger.info('Ennuagement = {0}%'.format(round(ennuagement*100, 2)))
             del tuiles_nuage
 
             if ennuagement == 0.0:
 
                 self.logger.info("Sauvegarde des images")
 
-                dossier = "{0}/{1}".format(self._repertory, date)
+                dossier = "{0}/{1}".format(self._repertory, date[:4])
+
+                self.logger.debug("Dossier image : {0}".format(dossier))
 
                 if not os.path.exists(dossier) :
                     os.mkdir(dossier)
 
-                self.ecriture_geotiff(image, "{0}/SENTINEL2.tif".format(dossier))
-                self.ecriture_geotiff(nuage, "{0}/NUAGE.tif".format(dossier))
+                self.ecriture_geotiff(image, "{0}/{1}.tif".format(dossier,date))
+        else :
+            del tuiles_nuage
 
-            del nuage
         del image
\ No newline at end of file
diff --git a/app/Constantes.py b/app/Constantes.py
index 6c65b51..34e5573 100644
--- a/app/Constantes.py
+++ b/app/Constantes.py
@@ -3,8 +3,11 @@
 
 import logging
 
-NIVEAU_DEFAUT = logging.DEBUG
+NIVEAU_DEFAUT 	= logging.DEBUG
 
 CLOUD_THRESHOLD = 0.0
 
-EPSG_PHYMOBAT = 2154
+EPSG_PHYMOBAT 	= 2154
+
+QGIS_PREFIX 	= "/usr"  
+
diff --git a/app/Outils.py b/app/Outils.py
index 7b8f835..928cef2 100644
--- a/app/Outils.py
+++ b/app/Outils.py
@@ -7,7 +7,7 @@ from logging.handlers import RotatingFileHandler
 import signal
 from contextlib import contextmanager
 import inspect
-import traceback
+from osgeo import gdal
 
 try:
 	import Constantes
@@ -100,4 +100,11 @@ def limitation_temporelle(secondes):
 #     with limitation_temporelle(temps_en_seconde):
 #         appel_fonction()
 # except TimeoutException:
-#     pass
\ No newline at end of file
+#     pass
+
+def clip(image, cut, form="Mem", dst=""):
+        
+	option_clip = gdal.WarpOptions(cropToCutline=True,\
+	cutlineDSName=cut, outputType=gdal.GDT_Float32 , format=form,  dstNodata='NaN')
+
+	return gdal.Warp(dst, image, options=option_clip)
\ No newline at end of file
diff --git a/app/Processing.py b/app/Processing.py
index e9dca86..6270a58 100644
--- a/app/Processing.py
+++ b/app/Processing.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 # -*- coding: utf-8 -*-
 #
 # This file is part of PHYMOBAT 2.0.
@@ -17,16 +17,19 @@
 # You should have received a copy of the GNU General Public License
 # along with PHYMOBAT 2.0.  If not, see <http://www.gnu.org/licenses/>.
 
-import os
+import os, sys
 import numpy as np
-import configparser 
+import configparser
+import glob 
+from osgeo import gdal, ogr, osr
 
-from app.Toolbox import Toolbox
+import otbApplication as otb
 
 # Class group image
 from app.Archive import Archive
 
 import app.Constantes as Constantes
+import app.Outils as Outils
 
 from collections import defaultdict
 from multiprocessing import Process
@@ -34,65 +37,13 @@ from multiprocessing.managers import BaseManager, DictProxy
 
 class Processing(object):
 	
-	"""
-		Main processing.
-		
-		This way is broken down into 2 parts :
-			- Image Processing (Search, download and processing)
-			- Classification 
-
-		**Main parameters**
-		
-		@param captor_project: Satellite captor name
-		@type captor_project: str
-
-		@param classif_year: Classification year
-		@type classif_year: str
-
-		@param nb_avalaible_images: Number download available images
-		@type nb_avalaible_images: int
-
-		@param path_folder_dpt: Main folder path
-		@type path_folder_dpt: str
-		
-		@param folder_processing: Processing folder name. By default : 'Traitement'
-		@type folder_processing: str
-		
-		@param path_area: Study area shapefile
-		@type path_area: str
-		
-		**Id information to download on theia platform**
-		
-		@param user: Connexion Username
-		@type user: str
-
-		@param password: Connexion Password
-		@type password: str
-		
-		**Output parameters**
-		
-		@param output_name_moba: Output classification shapefile 
-		@type output_name_moba: str
-
-		@param out_fieldname_carto: Output shapefile field name
-		@type out_fieldname_carto: list of str
-
-		@param out_fieldtype_carto: Output shapefile field type
-		@type out_fieldtype_carto: list of str (eval ogr pointer)
-		
-	"""
-	
 	def __init__(self):
 		# List of output raster path
 		self.raster_path = defaultdict(dict)
 				
 	def i_download(self):
 		"""
-			Interface function to download archives on the website Theia Land. This function extract 
-			the number of downloadable image with :func:`Archive.Archive.listing`.
-			
-			Then, this function download :func:`Archive.Archive.download` and unzip :func:`Archive.Archive.decompress` images
-			in the archive folder (**folder_archive**).
+			Méthode pour télécharger les images sur le site Theia Land.
 		"""
 
 		self.images_folder = "{0}/Images".format(self.path_folder_dpt)
@@ -100,152 +51,114 @@ class Processing(object):
 		if not os.path.exists(self.images_folder) :
 			os.makedirs(self.images_folder)
 
-		self.check_download = Archive(self.captor_project, self.classif_year, self.path_area, self.images_folder, self.path_area)
+		self.check_download = Archive(self.captor_project, self.classif_year, self.path_area, self.images_folder, self.object_area)
 		self.nb_avalaible_images = self.check_download.listing()
 		self.check_download.download_auto(self.user, self.password, self.proxy)
-		self.check_download.decompress()
 
-	def i_img_sat(self):	
-		"""
-			Interface function to processing satellite images:
-			
-				1. Clip archive images and modify Archive class to integrate clip image path.
-				With :func:`Toolbox.clip_raster` in ``Toolbox`` module.
-			
-				2. Search cloud's percentage :func:`RasterSat_by_date.RasterSat_by_date.pourc_cloud`, 
-				select image and compute ndvi index :func:`RasterSat_by_date.RasterSat_by_date.calcul_ndvi`.
-				If cloud's percentage is greater than 40%, then not select and compute ndvi index.
-			
-				3. Compute temporal stats on ndvi index [min, max, std, min-max]. With :func:`Toolbox.calc_serie_stats` 
-				in ``Toolbox`` module.
-				
-				4. Create stats ndvi raster and stats cloud raster.
-				
-				>>> import RasterSat_by_date
-				>>> stats_test = RasterSat_by_date(class_archive, big_folder, one_date)
-				>>> stats_test.complete_raster(stats_test.create_raster(in_raster, stats_data, in_ds), stats_data)
-		"""
-		pass		
-		# current_list = Toolbox()
-		# current_list.vect = self.path_area
+		self.liste_dossier = dict()
 
-		# # Map projection of the image and the cloud mask
-		# for clip_index, clip in enumerate(self.check_download.list_img):
-			
-		# 	current_list.image = clip[3]
-			
-		# 	current_list.check_proj() # Check if projection is RFG93 
-		# 	self.check_download.list_img[clip_index][3] = current_list.clip_raster() # Multispectral images
-			
-		# 	current_list.image = clip[4]
-		# 	current_list.check_proj() # Check if projection is RFG93 
-
-		# 	self.check_download.list_img[clip_index][4] = current_list.clip_raster() # Cloud images
+		for dossier in sorted(glob.glob("{0}/*".format(self.images_folder))) :
+			if os.path.isdir(dossier) :
+				self.liste_dossier[os.path.basename(dossier)] = sorted([ x for x in glob.glob("{0}/*".format(dossier)) if x.endswith(".tif")] )
+	
 
-		# # Images pre-processing
-		# spectral_out = []
+	def polygonisation(self, dossier, image_seuil):
+		"""
+			Méthode effactuant la polygonisation des images seuillées
+		"""	
+		
+		dossier_resultats = "{0}/Resultats/{1}".format(self.path_folder_dpt, dossier)
+		sortie = "{0}/{1}.shp".format(dossier_resultats, os.path.basename(image_seuil)[:-4])
 
-		# check_L8 = RasterSat_by_date(self.check_download, self.folder_processing)
+		if not os.path.exists(dossier_resultats) :
+			os.makedirs(dossier_resultats)
 
-		# for date in self.check_download.single_date:
+		format			= "ESRI Shapefile"
+		dst_layername 	= None
+		dst_fieldname 	= "Type"
+		dst_field 		= 0
+		src_band_n 		= 1
 
-		# 	check_L8.mosaic_by_date(date)
-			 
-		# 	# Search cloud's percentage, select image and compute ndvi index 
-		# 	if check_L8.pourc_cloud() < Constantes.CLOUD_THRESHOLD :
-		# 		tmp = date
-		# 		check_L8.calcul_ndvi()
-		# 		tmp.extend(check_L8.cloudiness_pourcentage)
-		# 		spectral_out.append(tmp)
+		src_ds = gdal.Open(image_seuil)
+		srcband = src_ds.GetRasterBand(src_band_n)
 
-		# # Compute temporal stats on ndvi index [min, max, std, min-max]
-		# spectral_trans = np.transpose(np.array(spectral_out, dtype=object))
+		maskband = srcband.GetMaskBand()
 
-		# del spectral_out 
+		drv = ogr.GetDriverByName(format)
+		dst_ds = drv.CreateDataSource(sortie)
 
-		# # stats_name = ['Min', 'Date', 'Max', 'Std', 'MaxMin']
-		# stats_name = ['Min', 'Max']
-		# path_stat = current_list.calc_serie_stats(spectral_trans, stats_name, self.folder_processing)
+		srs = osr.SpatialReference()
+		srs.ImportFromWkt(src_ds.GetProjectionRef())
 
-		# for idx, i in enumerate(stats_name):
-		# 	self.out_ndvistats_folder_tab[i] = path_stat[idx]
+		dst_layer = dst_ds.CreateLayer('out', geom_type=ogr.wkbPolygon, srs=srs)
 
-		# check_L8.logger.close()
-		# current_list.logger.close()
+		fd = ogr.FieldDefn(dst_fieldname, ogr.OFTInteger)
+		dst_layer.CreateField(fd)
+		
+		gdal.Polygonize( srcband, maskband, dst_layer, dst_field)
 
-	def i_vhrs(self):
+	def i_images_processing(self): 
 		"""
-			Interface function to processing VHRS images. 
-			It create two OTB texture images : 
-				func: `Vhrs.Vhrs` : SFS Texture and Haralick Texture
+			Méthode appelant le calcul du MSAVI2 via OTB
+			et effectuant le seuillage via MathBandX (OTB) 
 		"""
 
-		############################## Create texture image ##############################
-		
-		# Clip orthography image 
+		dossier_etude = "{0}/Zone_etude".format(self.path_folder_dpt)
+		dossier_MSAVI = "{0}/MSAVI2".format(self.path_folder_dpt)
+		dossier_seuil = "{0}/Seuillage".format(self.path_folder_dpt)
 
-		pass
+		if not os.path.exists(dossier_etude) :
+			os.makedirs(dossier_etude)
 
-		# current_path_ortho = Toolbox()
+		if not os.path.exists(dossier_MSAVI) :
+			os.makedirs(dossier_MSAVI)
 
-		# current_path_ortho.image = self.path_ortho
-		# current_path_ortho.vect = self.path_area
-		# current_path_ortho.output = "{0}/MosaiqueOrtho/Clip_{1}".format(self.folder_processing, os.path.basename(self.path_ortho))
-		
-		# self.path_ortho = current_path_ortho.output
-		# path_ortho = current_path_ortho.clip_raster()
+		if not os.path.exists(dossier_seuil) :
+			os.makedirs(dossier_seuil)
 		
-		# texture_irc = Vhrs(path_ortho, self.mp)
-		# self.out_ndvistats_folder_tab['sfs']      = texture_irc.out_sfs
-		# self.out_ndvistats_folder_tab['haralick'] = texture_irc.out_haralick
+		otb_MathBand = otb.Registry.CreateApplication("BandMathX")
+		otb_MathBand.SetParameterString("exp", "(im1b1 > -10000 ? (im1b1 > 0.025 ? (im1b1 < 0.15 ? 2 : 3 ) : 1) : im1b1)" )
 
-		# current_path_ortho.logger.close()
-		# texture_irc.logger.close()
-		
-	def i_images_processing(self): 
-		"""
-			Interface function to launch processing VHRS images : func:`i_vhrs` 
-			and satellite images :func:`i_img_sat` in multi-processing.
-		"""
+		otb_MSAVI = otb.Registry.CreateApplication("RadiometricIndices")
+		otb_MSAVI.SetParameterInt("channels.blue", 1)
+		otb_MSAVI.SetParameterInt("channels.green", 2)
+		otb_MSAVI.SetParameterInt("channels.red", 3)
+		otb_MSAVI.SetParameterInt("channels.nir", 4)
+		otb_MSAVI.SetParameterStringList("list", ["Vegetation:MSAVI2"])
 
-		pass
+		for dossier in self.liste_dossier :
 
-		# mgr = BaseManager()
-		# mgr.register('defaultdict', defaultdict, DictProxy)
-		# mgr.start()
+			nom_dst = "{0}/{1}".format(dossier_etude, dossier)
+			nom_msavi = "{0}/{1}".format(dossier_MSAVI, dossier)
+			nom_seuil = "{0}/{1}".format(dossier_seuil, dossier)
+
+			if not os.path.exists(nom_dst):
+				os.makedirs(nom_dst)
+
+			if not os.path.exists(nom_msavi):
+				os.makedirs(nom_msavi)
+
+			if not os.path.exists(nom_seuil):
+				os.makedirs(nom_seuil)
+
+			for img in self.liste_dossier[dossier] :
+				chemin_dst = "{0}/{1}".format(nom_dst, os.path.basename(img))
+				Outils.clip(img, self.object_area, form="GTiff", dst=chemin_dst)
+				
+				chemin_msavi = "{0}/{1}".format(nom_msavi, os.path.basename(img))
+				chemin_seuil = "{0}/{1}".format(nom_seuil, os.path.basename(img))
+
+				otb_MSAVI.SetParameterString("in", chemin_dst)
+				otb_MSAVI.SetParameterString("out", chemin_msavi)
+				otb_MSAVI.ExecuteAndWriteOutput()
+				# otb_MSAVI.Execute()
+
+				otb_MathBand.SetParameterStringList("il", [chemin_msavi])
+				# otb_MathBand.AddImageToParameterInputImageList("il",otb_MSAVI.GetParameterOutputImage("out"));
+				otb_MathBand.SetParameterString("out", chemin_seuil)
+
+				otb_MathBand.ExecuteAndWriteOutput()
+
+				self.polygonisation(dossier, chemin_seuil)
 
-		# self.out_ndvistats_folder_tab = mgr.defaultdict(list)		
-		
-		# p_img_sat = Process(target=self.i_img_sat)
-		# p_vhrs = Process(target=self.i_vhrs)
 
-		# self.logger.debug("Multiprocessing : {0}".format(self.mp))
-		
-		# if self.mp == Constantes.MULTIPROCESSING_DISABLE:
-		# 	p_img_sat.start()
-		# 	p_img_sat.join()
-		# 	p_vhrs.start()
-		# 	p_vhrs.join()	
-		# else :
-		# 	p_img_sat.start()
-		# 	p_vhrs.start()
-		# 	p_img_sat.join()
-		# 	p_vhrs.join()
-
-		# ###################################################################
-
-		# self.raster_path["Min"]["PATH"] = self.out_ndvistats_folder_tab['Min']
-		# self.raster_path["Min"]["BAND"] = 1
-		
-		# for i in range(1,7) :
-		# 	self.raster_path["SFS_{0}".format(i)]["PATH"] = self.out_ndvistats_folder_tab['sfs']
-		# 	self.raster_path["SFS_{0}".format(i)]["BAND"] = i
-
-		# for i in range(1,9) :
-		# 	self.raster_path["HARALICK_{0}".format(i)]["PATH"] = self.out_ndvistats_folder_tab['haralick']
-		# 	self.raster_path["HARALICK_{0}".format(i)]["BAND"] = i
-					
-		# self.raster_path["MAX"]["PATH"] = self.out_ndvistats_folder_tab['Max']
-		# self.raster_path["MAX"]["BAND"] = 1
-		
-		# self.logger.info("End of images processing !")
diff --git a/app/Projet_qgis.py b/app/Projet_qgis.py
new file mode 100644
index 0000000..cb71fb0
--- /dev/null
+++ b/app/Projet_qgis.py
@@ -0,0 +1,180 @@
+#! /usr/bin/python3
+# -*- coding: utf-8 -*-
+
+import sys, os
+
+import glob
+
+from osgeo import gdal, ogr, osr
+from qgis.core import *
+from qgis.gui import *
+from PyQt5.QtCore import QFileInfo
+from PyQt5.QtCore import *
+
+import app.Constantes as Constantes
+
+class Projet_qgis(object):
+	"""
+		docstring for Projet_qgis
+	"""
+
+	def __init__(self, nom_projet, style_image, style_classification):
+		super(Projet_qgis, self).__init__()
+
+		QgsApplication.setPrefixPath(Constantes.QGIS_PREFIX, True) 
+		self.qgs = QgsApplication([], True)
+
+		self.qgs.initQgis()
+		self.projet = QgsProject.instance()
+
+		self.projet.read(os.path.abspath(nom_projet))
+
+		self.racine = self.projet.layerTreeRoot()
+
+		self.style_image = style_image
+		self.style_classification = style_classification
+
+	def ajout_classif(self, chemin_classif):
+		
+		fileInfo = QFileInfo(chemin_classif)
+		path = fileInfo.filePath()
+
+		classification = QgsVectorLayer(path, "Classification")
+		classification.loadNamedStyle(self.style_classification)
+
+		self.projet.addMapLayer(classification, False)
+		self.date.addLayer(classification)
+
+
+	def ajout_image(self, chemin_image):
+		fileInfo = QFileInfo(chemin_image)
+		path = fileInfo.filePath()
+
+		image = QgsRasterLayer(path, "Image")
+		image.loadNamedStyle(self.style_image)
+
+		self.projet.addMapLayer(image, False)
+		self.date.addLayer(image)	
+
+	def ajout_sous_groupe(self, image, classif):
+		# print("{0} - {1}".format(image, classif))
+		# print(self.annee)
+
+		date = os.path.basename(image)[:-4]
+		
+		if self.annee.findGroup(date) :
+			self.date = self.annee.findGroup(date)
+		else :
+			self.date = self.annee.addGroup(date)
+
+		self.ajout_classif(classif)
+		self.ajout_image(image)
+
+		self.date = None
+
+	def ajout_groupe(self, dossier_image, dossier_classif):
+		liste_image 	= sorted([x for x in glob.glob("{0}/*".format(dossier_image)) if x.endswith('.tif')])
+		liste_classif 	= sorted([x for x in glob.glob("{0}/*".format(dossier_classif)) if x.endswith('.shp')])
+		
+		assert len(liste_image) == len(liste_classif), "Nombre d'image et de classification différent"
+		
+		if self.racine.findGroup(os.path.basename(dossier_image)) :
+			self.annee = self.racine.findGroup(os.path.basename(dossier_image))
+		else :
+			self.annee = self.racine.addGroup(os.path.basename(dossier_image))
+
+		for i in range(len(liste_image)):
+			self.ajout_sous_groupe(liste_image[i], liste_classif[i])
+
+		self.annee = None
+
+	def run(self, path_folder_dpt):
+		
+		self.images_folder 			= "{0}/Images".format(path_folder_dpt)
+		self.classification_folder 	= "{0}/Resultats".format(path_folder_dpt)
+
+		liste_dossiers_image 	= sorted([x for x in glob.glob("{0}/*".format(self.images_folder)) if os.path.isdir(x)])
+		liste_dossiers_classif 	= sorted([x for x in glob.glob("{0}/*".format(self.classification_folder)) if os.path.isdir(x)])
+
+		assert len(liste_dossiers_image) == len(liste_dossiers_classif), "Nombre d'image et de classification différent"
+
+		for i in range(len(liste_dossiers_image)):
+			self.ajout_groupe(liste_dossiers_image[i], liste_dossiers_classif[i])
+
+		self.projet.write()
+
+	def close(self):
+		self.qgs.exitQgis()
+
+
+
+# def main(argv=sys.argv):
+	
+# 	if racine.findGroup("2016") :
+# 		groupe = racine.findGroup("2016")
+# 	else :
+# 		groupe = racine.addGroup("2016")
+
+# 	# if groupe.findGroup("20160119") :
+# 	# 	sous_groupe = groupe.findGroup("20160119")
+# 	# else :
+# 	# 	sous_groupe = groupe.addGroup("20160119")
+
+# 	if not groupe.findGroup("20160119") :
+
+# 		print("1")
+# 		sous_groupe = groupe.addGroup("20160119")
+
+# 		fileInfo = QFileInfo(shapefile)
+# 		path = fileInfo.filePath()
+
+# 		segmentation = QgsVectorLayer(path, "Classification")
+# 		segmentation.loadNamedStyle(style_segmentation)
+
+# 		projet.addMapLayer(segmentation, False)
+# 		sous_groupe.addLayer(segmentation)
+
+# 		fileInfo = QFileInfo(raster)
+# 		path = fileInfo.filePath()
+
+# 		image = QgsRasterLayer(path, "Image")
+# 		image.loadNamedStyle(style_image)
+
+# 		projet.addMapLayer(image, False)
+# 		sous_groupe.addLayer(image)	
+
+# 		projet.setCrs(image.crs())
+
+# 		projet.write()
+
+# 	if not groupe.findGroup("20160200") :
+
+# 		print("2")
+# 		sous_groupe = groupe.addGroup("20160200")
+
+# 		fileInfo = QFileInfo(shapefile)
+# 		path = fileInfo.filePath()
+
+# 		segmentation = QgsVectorLayer(path, "Classification")
+# 		segmentation.loadNamedStyle(style_segmentation)
+
+# 		projet.addMapLayer(segmentation, False)
+# 		sous_groupe.addLayer(segmentation)
+
+# 		fileInfo = QFileInfo(raster)
+# 		path = fileInfo.filePath()
+
+# 		image = QgsRasterLayer(path, "Image")
+# 		image.loadNamedStyle(style_image)
+
+# 		projet.addMapLayer(image, False)
+# 		sous_groupe.addLayer(image)	
+
+# 		projet.setCrs(image.crs())
+
+# 		projet.write()
+
+# 	segmentation = None
+# 	image = None
+
+	
diff --git a/app/Toolbox.py b/app/Toolbox.py
deleted file mode 100644
index f5ee65f..0000000
--- a/app/Toolbox.py
+++ /dev/null
@@ -1,149 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-#
-# This file is part of PHYMOBAT 1.2.
-# Copyright 2016 Sylvio Laventure (IRSTEA - UMR TETIS)
-# 
-# PHYMOBAT 1.2 is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-# 
-# PHYMOBAT 1.2 is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-# 
-# You should have received a copy of the GNU General Public License
-# along with PHYMOBAT 1.2.  If not, see <http://www.gnu.org/licenses/>.
-
-from datetime import date
-
-import otbApplication as otb
-
-import os, sys, subprocess, glob
-import numpy as np
-import json
-import osgeo.gdal
-
-import app.Constantes
-import app.Outils
-
-class Toolbox(object):
-	"""
-		Class used to grouped small tools to cut raster or compute statistics on a raster. 
-			
-		:param imag: Input image (path)
-		:type imag: str
-		
-		:param vect: Extent shapefile (path)
-		:type vect: str
-	"""
-	
-	def __init__(self):
-		"""
-			Create a new 'Toolbox' instance
-		"""
-		self.image = None
-		self.vect = None
-		self.output = None
-		self.logger = Outils.Log("log", "Toolbox")
-	
-	def clip_raster(self, **kwargs):
-		"""
-			Function to clip a raster with a vector. 
-			The raster created will be in the same folder than the input raster.
-			With a prefix 'Clip_'.
-			
-			:kwargs: **rm_rast** (int) - 0 (by default) or 1. Variable to remove the output raster. 0 to keep and 1 to remove.
-			
-			:returns: str -- variable **outclip**, output raster clip (path).
-		"""
-		
-		rm_rast = kwargs['rm_rast'] if kwargs.get('rm_rast') else 0
-
-		if self.output is None :
-			outclip = "{0}/Clip_{1}".format(os.path.dirname(self.image), os.path.basename(self.image))
-		else :
-			if not os.path.exists(os.path.dirname(self.output)) :
-				os.mkdir(os.path.dirname(self.output))
-
-			outclip = self.output
-
-		options = osgeo.gdal.WarpOptions(dstNodata=-10000, cropToCutline=True,\
-		cutlineDSName=self.vect,outputType=osgeo.gdal.GDT_Int16 , format='GTiff')
-
-		if not os.path.exists(outclip) or rm_rast == 1:
-			self.logger.info("Raster clip of {0}".format(os.path.basename(self.image)))
-			osgeo.gdal.Warp(outclip, self.image, options=options)
-					
-		return outclip
-
-		
-	def calc_serie_stats(self, table, stats_name, output_folder):
-		"""
-			Function to compute stats on temporal cloud and ndvi spectral table
-			Ndvi stats : min max
-			
-			@param table: Spectral data, cloud raster and ndvi raster
-			@type table: numpy.ndarray
-			
-			@returns: list of numpy.ndarray -- variable **account_stats**, list of temporal NDVI stats.
-						  
-						numpy.ndarray -- variable **account_cloud**, pixel number clear on the area.
-		""" 
-
-		ind = ['min({0})', 'max({0})']
-		no_data_value = [99999, -10000]
-
-		stack_ndvi = list(table[5])
-		
-		image = "im{0}b1"
-
-		otb_MathBand = otb.Registry.CreateApplication("BandMathX")
-		otb_MathBand.SetParameterStringList("il", stack_ndvi)
-
-		path_stats = []
-
-		for idx, i in enumerate(ind):
-
-			expression = "({0} != {1} ? {0} : im1b1)".format(\
-				i.format(",".join("({0}>-1000 ? {0} : {1})".format(\
-					image.format(j+1), no_data_value[idx]) for j in range(len(stack_ndvi)))), no_data_value[idx])
-
-			out_ndvistats_raster = "{0}/{1}.TIF".format(output_folder, stats_name[idx])
-
-			path_stats.append(out_ndvistats_raster)
-
-			otb_MathBand.SetParameterString("exp",expression)
-			otb_MathBand.SetParameterString("out", out_ndvistats_raster)
-
-			otb_MathBand.SetParameterOutputImagePixelType("out", otb.ImagePixelType_float)
-
-			otb_MathBand.ExecuteAndWriteOutput()
-
-		return path_stats
-
-	
-	def check_proj(self):
-		"""
-			Function to check if raster's projection is RFG93. 
-			For the moment, PHYMOBAT works with one projection only Lambert 93 EPSG:2154 
-		"""
-
-		# Projection for PHYMOBAT
-
-		proj_phymobat = 'AUTHORITY["EPSG","{0}"]'.format(Constantes.EPSG_PHYMOBAT)
-
-		info_gdal = 'gdalinfo -json {0}'.format(self.image)
-		info_json = json.loads(subprocess.check_output(info_gdal, shell=True))
-		
-		# Check if projection is in Lambert 93
-		if not proj_phymobat in info_json['coordinateSystem']['wkt']:
-			output_proj = "{0}_L93.tif".format(self.image[:-4])
-			reproj = "gdalwarp -t_srs EPSG: {0} {1} {2}".format(Constantes.EPSG_PHYMOBAT, self.image, output_proj)
-			os.system(reproj)
-			# Remove old file and rename new file like the old file
-			os.remove(self.image)
-			os.rename(output_proj, self.image)
-		
\ No newline at end of file
diff --git a/app/style_classification.qml b/app/style_classification.qml
new file mode 100644
index 0000000..430adc5
--- /dev/null
+++ b/app/style_classification.qml
@@ -0,0 +1,205 @@
+<!DOCTYPE qgis PUBLIC 'http://mrcc.com/qgis.dtd' 'SYSTEM'>
+<qgis minScale="1e+8" simplifyLocal="1" labelsEnabled="0" simplifyDrawingHints="1" simplifyDrawingTol="1" version="3.4.1-Madeira" styleCategories="AllStyleCategories" simplifyAlgorithm="0" readOnly="0" hasScaleBasedVisibilityFlag="0" maxScale="0" simplifyMaxScale="1">
+  <flags>
+    <Identifiable>1</Identifiable>
+    <Removable>1</Removable>
+    <Searchable>1</Searchable>
+  </flags>
+  <renderer-v2 type="categorizedSymbol" forceraster="0" enableorderby="0" attr="Type" symbollevels="0">
+    <categories>
+      <category value="1" label="Eau" symbol="0" render="true"/>
+      <category value="2" label="Mixte" symbol="1" render="true"/>
+      <category value="3" label="Herbier" symbol="2" render="true"/>
+    </categories>
+    <symbols>
+      <symbol clip_to_extent="1" type="fill" alpha="1" name="0">
+        <layer pass="0" enabled="1" class="SimpleFill" locked="0">
+          <prop k="border_width_map_unit_scale" v="3x:0,0,0,0,0,0"/>
+          <prop k="color" v="1,60,235,255"/>
+          <prop k="joinstyle" v="bevel"/>
+          <prop k="offset" v="0,0"/>
+          <prop k="offset_map_unit_scale" v="3x:0,0,0,0,0,0"/>
+          <prop k="offset_unit" v="MM"/>
+          <prop k="outline_color" v="35,35,35,255"/>
+          <prop k="outline_style" v="solid"/>
+          <prop k="outline_width" v="0.26"/>
+          <prop k="outline_width_unit" v="MM"/>
+          <prop k="style" v="solid"/>
+          <data_defined_properties>
+            <Option type="Map">
+              <Option type="QString" value="" name="name"/>
+              <Option name="properties"/>
+              <Option type="QString" value="collection" name="type"/>
+            </Option>
+          </data_defined_properties>
+        </layer>
+      </symbol>
+      <symbol clip_to_extent="1" type="fill" alpha="1" name="1">
+        <layer pass="0" enabled="1" class="SimpleFill" locked="0">
+          <prop k="border_width_map_unit_scale" v="3x:0,0,0,0,0,0"/>
+          <prop k="color" v="255,255,0,255"/>
+          <prop k="joinstyle" v="bevel"/>
+          <prop k="offset" v="0,0"/>
+          <prop k="offset_map_unit_scale" v="3x:0,0,0,0,0,0"/>
+          <prop k="offset_unit" v="MM"/>
+          <prop k="outline_color" v="35,35,35,255"/>
+          <prop k="outline_style" v="solid"/>
+          <prop k="outline_width" v="0.26"/>
+          <prop k="outline_width_unit" v="MM"/>
+          <prop k="style" v="solid"/>
+          <data_defined_properties>
+            <Option type="Map">
+              <Option type="QString" value="" name="name"/>
+              <Option name="properties"/>
+              <Option type="QString" value="collection" name="type"/>
+            </Option>
+          </data_defined_properties>
+        </layer>
+      </symbol>
+      <symbol clip_to_extent="1" type="fill" alpha="1" name="2">
+        <layer pass="0" enabled="1" class="SimpleFill" locked="0">
+          <prop k="border_width_map_unit_scale" v="3x:0,0,0,0,0,0"/>
+          <prop k="color" v="77,200,0,255"/>
+          <prop k="joinstyle" v="bevel"/>
+          <prop k="offset" v="0,0"/>
+          <prop k="offset_map_unit_scale" v="3x:0,0,0,0,0,0"/>
+          <prop k="offset_unit" v="MM"/>
+          <prop k="outline_color" v="35,35,35,255"/>
+          <prop k="outline_style" v="solid"/>
+          <prop k="outline_width" v="0.26"/>
+          <prop k="outline_width_unit" v="MM"/>
+          <prop k="style" v="solid"/>
+          <data_defined_properties>
+            <Option type="Map">
+              <Option type="QString" value="" name="name"/>
+              <Option name="properties"/>
+              <Option type="QString" value="collection" name="type"/>
+            </Option>
+          </data_defined_properties>
+        </layer>
+      </symbol>
+    </symbols>
+    <source-symbol>
+      <symbol clip_to_extent="1" type="fill" alpha="1" name="0">
+        <layer pass="0" enabled="1" class="SimpleFill" locked="0">
+          <prop k="border_width_map_unit_scale" v="3x:0,0,0,0,0,0"/>
+          <prop k="color" v="213,180,60,255"/>
+          <prop k="joinstyle" v="bevel"/>
+          <prop k="offset" v="0,0"/>
+          <prop k="offset_map_unit_scale" v="3x:0,0,0,0,0,0"/>
+          <prop k="offset_unit" v="MM"/>
+          <prop k="outline_color" v="35,35,35,255"/>
+          <prop k="outline_style" v="solid"/>
+          <prop k="outline_width" v="0.26"/>
+          <prop k="outline_width_unit" v="MM"/>
+          <prop k="style" v="solid"/>
+          <data_defined_properties>
+            <Option type="Map">
+              <Option type="QString" value="" name="name"/>
+              <Option name="properties"/>
+              <Option type="QString" value="collection" name="type"/>
+            </Option>
+          </data_defined_properties>
+        </layer>
+      </symbol>
+    </source-symbol>
+    <colorramp type="randomcolors" name="[source]"/>
+    <rotation/>
+    <sizescale/>
+  </renderer-v2>
+  <customproperties>
+    <property value="0" key="embeddedWidgets/count"/>
+    <property key="variableNames"/>
+    <property key="variableValues"/>
+  </customproperties>
+  <blendMode>0</blendMode>
+  <featureBlendMode>0</featureBlendMode>
+  <layerOpacity>1</layerOpacity>
+  <SingleCategoryDiagramRenderer diagramType="Histogram" attributeLegend="1">
+    <DiagramCategory penColor="#000000" minimumSize="0" backgroundAlpha="255" diagramOrientation="Up" penWidth="0" scaleBasedVisibility="0" minScaleDenominator="0" barWidth="5" rotationOffset="270" sizeScale="3x:0,0,0,0,0,0" penAlpha="255" height="15" scaleDependency="Area" lineSizeType="MM" lineSizeScale="3x:0,0,0,0,0,0" maxScaleDenominator="1e+8" backgroundColor="#ffffff" enabled="0" sizeType="MM" labelPlacementMethod="XHeight" width="15" opacity="1">
+      <fontProperties description="Ubuntu,11,-1,5,50,0,0,0,0,0" style=""/>
+    </DiagramCategory>
+  </SingleCategoryDiagramRenderer>
+  <DiagramLayerSettings obstacle="0" zIndex="0" dist="0" priority="0" linePlacementFlags="18" placement="1" showAll="1">
+    <properties>
+      <Option type="Map">
+        <Option type="QString" value="" name="name"/>
+        <Option name="properties"/>
+        <Option type="QString" value="collection" name="type"/>
+      </Option>
+    </properties>
+  </DiagramLayerSettings>
+  <geometryOptions removeDuplicateNodes="0" geometryPrecision="0">
+    <activeChecks/>
+    <checkConfiguration/>
+  </geometryOptions>
+  <fieldConfiguration>
+    <field name="Type">
+      <editWidget type="Range">
+        <config>
+          <Option/>
+        </config>
+      </editWidget>
+    </field>
+  </fieldConfiguration>
+  <aliases>
+    <alias field="Type" index="0" name=""/>
+  </aliases>
+  <excludeAttributesWMS/>
+  <excludeAttributesWFS/>
+  <defaults>
+    <default field="Type" expression="" applyOnUpdate="0"/>
+  </defaults>
+  <constraints>
+    <constraint field="Type" exp_strength="0" unique_strength="0" constraints="0" notnull_strength="0"/>
+  </constraints>
+  <constraintExpressions>
+    <constraint field="Type" exp="" desc=""/>
+  </constraintExpressions>
+  <expressionfields/>
+  <attributeactions>
+    <defaultAction value="{00000000-0000-0000-0000-000000000000}" key="Canvas"/>
+  </attributeactions>
+  <attributetableconfig sortExpression="" sortOrder="0" actionWidgetStyle="dropDown">
+    <columns>
+      <column type="field" name="Type" hidden="0" width="-1"/>
+      <column type="actions" hidden="1" width="-1"/>
+    </columns>
+  </attributetableconfig>
+  <conditionalstyles>
+    <rowstyles/>
+    <fieldstyles/>
+  </conditionalstyles>
+  <editform tolerant="1"></editform>
+  <editforminit/>
+  <editforminitcodesource>0</editforminitcodesource>
+  <editforminitfilepath></editforminitfilepath>
+  <editforminitcode><![CDATA[# -*- coding: utf-8 -*-
+"""
+Les formulaires QGIS peuvent avoir une fonction Python qui sera appelée à l'ouverture du formulaire.
+
+Utilisez cette fonction pour ajouter plus de fonctionnalités à vos formulaires.
+
+Entrez le nom de la fonction dans le champ "Fonction d'initialisation Python".
+Voici un exemple à suivre:
+"""
+from qgis.PyQt.QtWidgets import QWidget
+
+def my_form_open(dialog, layer, feature):
+    geom = feature.geometry()
+    control = dialog.findChild(QWidget, "MyLineEdit")
+
+]]></editforminitcode>
+  <featformsuppress>0</featformsuppress>
+  <editorlayout>generatedlayout</editorlayout>
+  <editable>
+    <field editable="1" name="Type"/>
+  </editable>
+  <labelOnTop>
+    <field name="Type" labelOnTop="0"/>
+  </labelOnTop>
+  <widgets/>
+  <previewExpression>Type</previewExpression>
+  <mapTip></mapTip>
+  <layerGeometryType>2</layerGeometryType>
+</qgis>
diff --git a/app/style_image.qml b/app/style_image.qml
new file mode 100644
index 0000000..a7ae7c7
--- /dev/null
+++ b/app/style_image.qml
@@ -0,0 +1,46 @@
+<!DOCTYPE qgis PUBLIC 'http://mrcc.com/qgis.dtd' 'SYSTEM'>
+<qgis hasScaleBasedVisibilityFlag="0" maxScale="0" minScale="1e+8" version="3.4.1-Madeira" styleCategories="AllStyleCategories">
+  <flags>
+    <Identifiable>1</Identifiable>
+    <Removable>1</Removable>
+    <Searchable>1</Searchable>
+  </flags>
+  <customproperties>
+    <property value="false" key="WMSBackgroundLayer"/>
+    <property value="false" key="WMSPublishDataSourceUrl"/>
+    <property value="0" key="embeddedWidgets/count"/>
+    <property value="Value" key="identify/format"/>
+  </customproperties>
+  <pipe>
+    <rasterrenderer redBand="4" greenBand="3" opacity="1" type="multibandcolor" blueBand="2" alphaBand="-1">
+      <rasterTransparency/>
+      <minMaxOrigin>
+        <limits>CumulativeCut</limits>
+        <extent>WholeRaster</extent>
+        <statAccuracy>Estimated</statAccuracy>
+        <cumulativeCutLower>0.02</cumulativeCutLower>
+        <cumulativeCutUpper>0.98</cumulativeCutUpper>
+        <stdDevFactor>2</stdDevFactor>
+      </minMaxOrigin>
+      <redContrastEnhancement>
+        <minValue>-0.000294392</minValue>
+        <maxValue>0.279033</maxValue>
+        <algorithm>StretchToMinimumMaximum</algorithm>
+      </redContrastEnhancement>
+      <greenContrastEnhancement>
+        <minValue>0.0063666</minValue>
+        <maxValue>0.144947</maxValue>
+        <algorithm>StretchToMinimumMaximum</algorithm>
+      </greenContrastEnhancement>
+      <blueContrastEnhancement>
+        <minValue>0.0133541</minValue>
+        <maxValue>0.11239</maxValue>
+        <algorithm>StretchToMinimumMaximum</algorithm>
+      </blueContrastEnhancement>
+    </rasterrenderer>
+    <brightnesscontrast contrast="0" brightness="0"/>
+    <huesaturation colorizeGreen="128" colorizeRed="255" saturation="0" colorizeBlue="128" colorizeStrength="100" colorizeOn="0" grayscaleMode="0"/>
+    <rasterresampler maxOversampling="2"/>
+  </pipe>
+  <blendMode>0</blendMode>
+</qgis>
diff --git a/config.ini b/config.ini
index 95fe545..8ed914a 100644
--- a/config.ini
+++ b/config.ini
@@ -1,9 +1,12 @@
 [emprise]
-chemin = /media/commandre/8ce2dd88-1425-4161-a3c1-47f566c0285a/Roseliere_test/bagnas/emprise.shp
+chemin = /media/commandre/8ce2dd88-1425-4161-a3c1-47f566c0285a/Roseliere/bagnas/emprise.shp
 annee_debut = 2016
 
+[zone_etude]
+chemin = /media/commandre/8ce2dd88-1425-4161-a3c1-47f566c0285a/Roseliere/bagnas/grand_bagnas.shp
+
 [sortie]
-chemin = /media/commandre/8ce2dd88-1425-4161-a3c1-47f566c0285a/Roseliere_test/bagnas
+chemin = /media/commandre/8ce2dd88-1425-4161-a3c1-47f566c0285a/Roseliere/bagnas
 
 [theia]
 identifiant = sylvio.laventure@gmail.com
@@ -11,4 +14,10 @@ mdp = Sylvio.L34
 proxy = 
 
 [satellite]
-capteur = SENTINEL2
\ No newline at end of file
+capteur = SENTINEL2
+
+[style]
+image = /media/commandre/8ce2dd88-1425-4161-a3c1-47f566c0285a/Roseliere/app/style_image.qml
+
+classification = /media/commandre/8ce2dd88-1425-4161-a3c1-47f566c0285a/Roseliere/app/style_classification.qml
+
-- 
GitLab