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