diff --git a/Common/otb_numpy_proc.py b/Common/otb_numpy_proc.py new file mode 100644 index 0000000000000000000000000000000000000000..1465d0402ee9d60f825e514df165cb2a64908099 --- /dev/null +++ b/Common/otb_numpy_proc.py @@ -0,0 +1,221 @@ +import otbApplication as otb +import numpy as np +import sys +from time import sleep +import os, psutil +from functools import partial +import string, random +from gdal import BuildVRT + +def randomword(length): + return ''.join(random.choice(string.ascii_lowercase) for i in range(length)) + +def generate_stream_rois(img_size, n_lines): + streams = [] + n_streams = int(img_size[1]/n_lines) + for i in range(n_streams): + stream = {} + stream['sizex'] = img_size[0] + stream['sizey'] = n_lines + stream['startx'] = 0 + stream['starty'] = i * n_lines + streams.append(stream) + + if (img_size[1] % n_lines) : + stream = {} + stream['sizex'] = img_size[0] + stream['sizey'] = img_size[1] % n_lines + stream['startx'] = 0 + stream['starty'] = n_streams * n_lines + streams.append(stream) + + return streams + +def pad_format_tile(tile, img_size, pad): + new_tile = np.array(tile) + np.array([-pad, -pad, pad, pad]) + pad_out = [0,0,0,0] + + if new_tile[0] < 0: + pad_out[0] = pad + new_tile[0] = 0 + if new_tile[1] < 0: + pad_out[1] = pad + new_tile[1] = 0 + if new_tile[2] > img_size[0]-1: + pad_out[2] = pad + new_tile[2] = img_size[0]-1 + if new_tile[3] > img_size[1]-1: + pad_out[3] = pad + new_tile[3] = img_size[1]-1 + + new_tile[2] = new_tile[2] - new_tile[0] + 1 + new_tile[3] = new_tile[3] - new_tile[1] + 1 + pads1 = (pad_out[0], pad_out[2]) + pads2 = (pad_out[1], pad_out[3]) + + return ([int(x) for x in new_tile],pads1,pads2) + + +def generate_tile_rois(img_size, tile_size, pad = 0): + tiles = [] + n_tiles = [int(img_size[0]/tile_size[0]), int(img_size[1]/tile_size[1])] + for i in range(2): + if img_size[i] % tile_size[i] : + n_tiles[i] += 1 + for c in range(n_tiles[0]): + for r in range(n_tiles[1]): + pad_out = [0,0,0,0] + bbox = [c*tile_size[0],r*tile_size[1],min(c*tile_size[0]+tile_size[0]-1,img_size[0]-1),min(r*tile_size[1]+tile_size[1]-1,img_size[1]-1)] + tiles.append(pad_format_tile(bbox,img_size,pad)) + return tiles + +''' +def generate_stream_regions(img_size, n_lines): + streams = [] + n_streams = int(img_size[1]/n_lines) + for i in range(n_streams): + stream = otb.itkRegion() + stream['size'][0] = img_size[0] + stream['size'][1] = n_lines + stream['index'][0] = 0 + stream['index'][1] = i * n_lines + streams.append(stream) + + if (img_size[1] % n_lines) : + stream = otb.itkRegion() + stream['size'][0] = img_size[0] + stream['size'][1] = img_size[1] % n_lines + stream['index'][0] = 0 + stream['index'][1] = n_streams * n_lines + streams.append(stream) + + return streams +''' + +''' +def generate_streams(otb_pipeline): + otb_pipeline.Execute() + img_size = tuple(otb_pipeline.GetImageSize("out")) + stream_rois = generate_stream_rois(img_size,1000) + ext_roi_apps = [] + for roi in stream_rois : + print("setting roi : " + str(roi)) + app = otb.Registry.CreateApplication('ExtractROI') + app.SetParameterInputImage("in", otb_pipeline.GetParameterOutputImage("out")) + app.SetParameterInt("sizex", roi['sizex']) + app.SetParameterInt("sizey", roi['sizey']) + app.SetParameterInt("startx", roi['startx']) + app.SetParameterInt("starty", roi['starty']) + ext_roi_apps.append(app) + ext_roi_apps[-1].Execute() + + return ext_roi_apps +''' + +def stream_function(otb_in, function, tile_size, write_output_to=None, pad=None, out_key = 'out', work_dir='.', prefix = None): + otb_pipeline = to_otb_pipeline(otb_in) + + if prefix is None: + prefix = randomword(16) + + if pad is None: + pad = check_function_padding(function) + + otb_pipeline.Execute() + img_size = tuple(otb_pipeline.GetImageSize(out_key)) + + streams = generate_tile_rois(img_size,tile_size,pad) + + t = 1 + output_streams = [] + for stream in streams: + roi = otb.Registry.CreateApplication('ExtractROI') + roi.SetParameterInputImage('in', otb_pipeline.GetParameterOutputImage(out_key)) + roi.SetParameterInt('startx', stream[0][0]) + roi.SetParameterInt('starty', stream[0][1]) + roi.SetParameterInt('sizex', stream[0][2]) + roi.SetParameterInt('sizey', stream[0][3]) + roi.Execute() + img = roi.ExportImage('out') + + # Launch numpy function ! + padw = (stream[1],stream[2]) if img['array'].ndim == 2 else (stream[1],stream[2],(0,0)) + img['array'] = np.ascontiguousarray(function(np.pad(img['array'],padw))) + + cpy = otb.Registry.CreateApplication('ExtractROI') + cpy.ImportVectorImage('in', img) + output_streams.append(os.path.join(work_dir, prefix + '_' + str(t) + '.tif')) + cpy.SetParameterString('out', output_streams[-1]) + cpy.ExecuteAndWriteOutput() + print(cpy.GetImageSize('out')) + app = None + img = None + t += 1 + + vrt = os.path.join(work_dir, prefix + '.vrt') + BuildVRT(vrt, output_streams) + output_streams.append(vrt) + + new_pipeline = otb.Registry.CreateApplication('ExtractROI') + new_pipeline.SetParameterString('in', vrt) + + if write_output_to is not None: + new_pipeline.SetParameterString('out', write_output_to) + new_pipeline.ExecuteAndWriteOutput() + [os.remove(f) for f in output_streams] + return write_output_to, [] + else: + return new_pipeline, output_streams + +''' +def merge_streams(ext_roi_apps): + app = otb.Registry.CreateApplication('Mosaic') + i=0 + for roi in ext_roi_apps: + img = roi.ExportImage('out') + img['array'] /= 10 + print(psutil.Process(os.getpid()).memory_info().rss) + sleep(5) + app.ImportVectorImage('il',img,i) + i += 1 + return app +''' + +def test_function(arr): + #return arr[2:-2,2:-2,:] + return np.sum(arr,axis=2)[6:-6,6:-6] + +def check_function_padding(function, test_tile_size=(100,100)): + try: + out = function(np.ones((test_tile_size))) + except: + out = function(np.ones((test_tile_size[0],test_tile_size[1],3))) + return int(max(abs(test_tile_size[0] - out.shape[0]), abs(test_tile_size[1] - out.shape[1])) / 2) + +def to_otb_pipeline(obj): + if isinstance(obj, str): + ppl = otb.Registry.CreateApplication('ExtractROI') + ppl.SetParameterString('in',obj) + ppl.Execute() + elif isinstance(obj,otb.Application): + ppl = obj + elif isinstance(obj,list): # to-do, check if all sublists are lists of otbApp + ppl = obj + else: + sys.exit("Impossible to convert object ot OTB pipeline") + return ppl + +def do_something(fn): + # Create a smoothing application + app = otb.Registry.CreateApplication("ExtractROI") + app.SetParameterString("in", fn) + + app2, to_del = stream_function(app, partial(test_function), tile_size=(1000,1000)) + + app2.SetParameterString('out','pippo.tif') + app2.ExecuteAndWriteOutput() + + [os.remove(f) for f in to_del] + +if __name__ == "__main__": + do_something("/DATA/Koumbia/Tong/SENTINEL2_Aug_Oct_2017_RGB_NIR_NDVI.tif") diff --git a/Common/otb_vector_proc.py b/Common/otb_vector_proc.py new file mode 100644 index 0000000000000000000000000000000000000000..4fd5e8c53656277692851e6a5252e179f38dcba8 --- /dev/null +++ b/Common/otb_vector_proc.py @@ -0,0 +1,61 @@ +import gdal, ogr, osr +import numpy as np +import otb_numpy_proc +import otbApplication as otb +import os + +def pixels_to_vector(selection_array, otb_pipeline, out_vector, out_key='out', var_dict=None): + geo_transform = otb_pipeline.GetImageMetaData(out_key)['GeoTransform'] + epsg = int(otb_pipeline.GetImageMetaData(out_key)['ProjectionRef'].split('"')[-2]) + srs = osr.SpatialReference() + srs.ImportFromEPSG(epsg) + + drv = ogr.GetDriverByName('ESRI Shapefile') + ds = drv.CreateDataSource(out_vector) + ly = ds.CreateLayer(os.path.basename(os.path.splitext(out_vector)[0]), geom_type=ogr.wkbPoint, srs=srs) + ly.CreateField(ogr.FieldDefn('id', ogr.OFTInteger64)) + ly.CreateField(ogr.FieldDefn('px', ogr.OFTInteger64)) + ly.CreateField(ogr.FieldDefn('py', ogr.OFTInteger64)) + if var_dict is not None: + for v in var_dict: + if 'float' in str(v['array'].dtype): + typ = ogr.OFTReal + else: + typ = ogr.OFTInteger + if v['array'].ndim == 2: + for t in range(v['array'].shape[1]): + ly.CreateField(ogr.FieldDefn(v['prefix']+'_%d' % t, typ)) + else: + ly.CreateField(ogr.FieldDefn(v['prefix'], typ)) + + i = 0 + pix_pnts = np.where(selection_array) + for p in zip(pix_pnts[0]+0.5,pix_pnts[1]+0.5): + X = geo_transform[0] + p[1] * geo_transform[1] + Y = geo_transform[3] + p[0] * geo_transform[5] + gp = ogr.Geometry(ogr.wkbPoint) + gp.AddPoint(X,Y) + gf = ogr.Feature(ly.GetLayerDefn()) + gf.SetGeometry(gp) + gf.SetField('id', i) + gf.SetField('px', p[1]) + gf.SetField('py', p[0]) + if var_dict is not None: + for v in var_dict: + if v['array'].ndim == 2: + for t in range(v['array'].shape[1]): + gf.SetField(v['prefix']+'_%d' % t, float(v['array'][i,t])) + else: + gf.SetField(v['prefix'], float(v['array'][i])) + ly.CreateFeature(gf) + i += 1 + + ds = None + +if __name__ == '__main__': + p = otb_numpy_proc.to_otb_pipeline('./pippo.tif') + p.Execute() + arr = np.random.random((100, 100)) < 0.1 + drv = ogr.GetDriverByName('ESRI Shapefile') + drv.DeleteDataSource('./pippuzzo.shp') + pixels_to_vector(arr, p, 'pippuzzo.shp') diff --git a/TimeSeries/s1base.py b/TimeSeries/s1base.py new file mode 100644 index 0000000000000000000000000000000000000000..e8802bf454480a3fc8ae336b9aa5b8c8bc24266b --- /dev/null +++ b/TimeSeries/s1base.py @@ -0,0 +1,307 @@ +import otbApplication as otb +from otb_numpy_proc import to_otb_pipeline +import os +import glob +import numpy as np +import uuid +import xml.etree.ElementTree as ET +import shutil +from itertools import groupby + +class S1GRDPipeline: + # --- BEGIN SENSOR PROTOTYPE --- + NAME = 'S1-IW-GRD' + VAL_TYPE = otb.ImagePixelType_int16 + TMP_TYPE = otb.ImagePixelType_float + PTRN_dir = 'S1*_IW_GRD*.SAFE' + PTRN_ref = '-iw-grd-' + VH_name = 'vh' + PTRN = ['measurement/s1*-iw-grd-vh-*.tiff', 'measurement/s1*-iw-grd-vv-*.tiff'] + FEAT_exp = { + 'VH': 'im1b1', + 'VV': 'im2b1', + 'VH_db': '1000*log10(abs(im1b1)+1e-6)', + 'VV_db': '1000*log10(abs(im2b1)+1e-6)', + 'POL_RATIO': 'im1b1/im2b1' + } + NDT = 0.0 + + @classmethod + def _check(cls, x): + lst = os.path.basename(cls.PTRN_dir).split('*') + return all([t in os.path.basename(x) for t in lst]) + + @classmethod + def _img_id(cls, x): + if cls._check(x): + return '_'.join(x.split('_')[4:7]) + else: + return None + + @classmethod + def _img_date(cls, x): + if cls._check(x): + return x.split('_')[4].split('T')[0] + else: + return None + + @classmethod + def _check_roi(cls, x, roi, min_surf=0.0): + if cls._check(x): + if os.path.isdir(x): + er = otb.Registry.CreateApplication('ExtractROI') + bnd = glob.glob(os.path.join(x, cls.PTRN[0]))[0] + er.SetParameterString('in', bnd) + er.SetParameterString('mode', 'fit') + er.SetParameterString('mode.fit.im', roi) + try: + er.Execute() + except: + return False + arr = er.GetImageAsNumpyArray('out') + if (np.sum(arr != cls.NDT) / (arr.shape[0] * arr.shape[1])) <= min_surf: + return False + else: + return True + + @classmethod + def _check_direction(cls, x): + root = ET.parse(os.path.join(x, 'manifest.safe')).getroot() + for item in root.iter(): + if item.tag.endswith('pass'): + if item.text == 'ASCENDING': + return 0 + elif item.text == 'DESCENDING': + return 1 + else: + return None + + def _check_satellite(self, x): + return os.path.basename(x).split('_')[0].lower() + + + def __init__(self, fld, roi, temp_fld='/tmp', input_date_interval=None, roi_min_surf=0.0, + direction=None, satellite=None): + self.pipe = [] + self.files = [] + self.types = [] + self.out_p = [] + self.out_idx = [] + + self.id = str(uuid.uuid4()) + self.folder = os.path.abspath(fld) + self.temp_fld = temp_fld + os.sep + self.id + self.direction = direction + self.satellite = satellite + self.roi = roi + self.image_list = self.parse_folder(self.folder, roi_min_surf) + self.input_dates = [self._img_date(x) for x in self.image_list] + + if input_date_interval is not None: + idx = [i for i in range(len(self.input_dates)) if + input_date_interval[0] <= self.input_dates[i] <= input_date_interval[1]] + self.image_list = [self.image_list[i] for i in idx] + self.input_dates = [self.input_dates[i] for i in idx] + + for img in self.image_list: + for p in self.PTRN: + ifn, ofn = self.get_file(img, p) + self.append(to_otb_pipeline(ifn), ofn, self.VAL_TYPE, 'out', is_output=True) + + def __del__(self): + if os.path.exists(self.temp_fld): + shutil.rmtree(self.temp_fld) + + def parse_folder(self, fld, roi_min_surf=0.0): + print(glob.glob(os.path.join(fld, self.PTRN_dir))) + img_list = [os.path.abspath(x) for x in glob.glob(os.path.join(fld, self.PTRN_dir)) + if os.path.isdir(x) and self._check_roi(x, self.roi, roi_min_surf)] + + if self.satellite is not None: + img_list = [x for x in img_list if self._check_satellite(x) == self.satellite] + + asc, desc = [], [] + for x in img_list: + if self._check_direction(x) == 0: + asc.append(x) + elif self._check_direction(x) == 1: + desc.append(x) + + out = None + if self.direction == 'ascending': + out = asc + elif self.direction == 'descending': + out = desc + elif self.direction == None: + if len(asc) >= len(desc): + self.direction = 'ascending' + out = asc + else: + self.direction = 'descending' + out = desc + print('[INFO] No direction selected, returning majority : ' + self.direction) + return sorted(out, key=lambda x: self._img_id(x)) + + def get_file(self, img, ptrn): + in_fn = glob.glob(os.path.join(img, ptrn))[0] + out_fn = in_fn.replace(self.folder, '').lstrip(os.sep) + return in_fn, os.path.splitext(out_fn)[0] + '.tiff' + + def append(self, app, fname=None, ftype=None, outp=None, is_output=False): + if is_output: + self.out_idx.append(len(self.pipe)) + self.pipe.append(app) + self.files.append(fname) + self.types.append(ftype) + self.out_p.append(outp) + + def calibrate(self, lut='sigma'): + proc_idx = self.out_idx.copy() + self.out_idx = [] + for t in proc_idx: + sarcal = otb.Registry.CreateApplication('SARCalibration') + sarcal.SetParameterInputImage('in', self.pipe[t].GetParameterOutputImage(self.out_p[t])) + sarcal.SetParameterString('lut', lut) + sarcal.Execute() + fn = self.files[t].replace('.tiff', '_calib.tiff') + ty = self.TMP_TYPE + self.append(sarcal, fn, ty, 'out', is_output=True) + + def orthorectify(self, dem_fld, geoid=None, grid_spacing=40): + assert(os.path.isdir(dem_fld)) + proc_idx = self.out_idx.copy() + self.out_idx = [] + for t in proc_idx: + ortho = otb.Registry.CreateApplication('OrthoRectification') + ortho.SetParameterString('elev.dem', dem_fld) + if geoid is not None: + assert (os.path.exists(geoid)) + ortho.SetParameterString('elev.geoid', geoid) + ortho.SetParameterInputImage('io.in', self.pipe[t].GetParameterOutputImage(self.out_p[t])) + ortho.SetParameterInt('opt.gridspacing', grid_spacing) + ortho.SetParameterString('outputs.mode', 'orthofit') + ortho.SetParameterString('outputs.ortho', self.roi) + ortho.Execute() + fn = self.files[t].replace('.tiff', '_ortho.tiff') + ty = self.TMP_TYPE + self.append(ortho, fn, ty, 'io.out', is_output=True) + + def stitch(self): + proc_idx = self.out_idx.copy() + self.out_idx = [] + q = [[self.image_list.index(v) for v in list(i)] + for j, i in groupby(self.image_list, lambda x: self._img_date(x))] + for s in q: + if len(s) == 1: + self.append(self.pipe[proc_idx[2 * s[0]]], self.files[proc_idx[2 * s[0]]], + self.types[proc_idx[2 * s[0]]], self.out_p[proc_idx[2 * s[0]]], is_output=True) + self.append(self.pipe[proc_idx[2 * s[0] + 1]], self.files[proc_idx[2 * s[0] + 1]], + self.types[proc_idx[2 * s[0] + 1]], self.out_p[proc_idx[2 * s[0] + 1]], is_output=True) + else: + for k in range(2): + fn = self.files[proc_idx[2*s[0]+k]] + tm1 = os.path.basename(fn).split('-')[-4].split('t')[1] + tm2 = os.path.basename(fn).split('-')[-5].split('t')[1] + fn = fn.replace(tm1, 'xxxxxx').replace(tm2, 'xxxxxx') + bm = otb.Registry.CreateApplication('BandMathX') + [bm.AddImageToParameterInputImageList('il', self.pipe[proc_idx[2*u+k]].GetParameterOutputImage(self.out_p[proc_idx[2*u+k]])) for u in s] + bm.SetParameterString('exp', + 'vmax({' + ';'.join(['im%db1' % (i + 1) for i in range(len(s))]) + '})') + bm.Execute() + self.append(bm, fn, self.types[proc_idx[2*s[0]+k]], 'out', is_output=True) + + def multitemp_speckle_filter(self, win_size=3, outcore_on_disk=True): + proc_idx = self.out_idx.copy() + self.out_idx = [] + tmp_pipe = [] + tmp_fns = [] + ty = self.TMP_TYPE + for u in range(2): + oc = otb.Registry.CreateApplication('MultitempFilteringOutcore') + for t in proc_idx[u::2]: + oc.AddImageToParameterInputImageList('inl', self.pipe[t].GetParameterOutputImage(self.out_p[t])) + oc.SetParameterString('wr', str(win_size)) + if not outcore_on_disk: + oc.Execute() + oc_idx = len(self.pipe) + self.append(oc) + else: + if not os.path.exists(self.temp_fld): + os.makedirs(self.temp_fld) + oc_fn = os.path.join(self.temp_fld, 'outcore_{}.tif'.format(['vh', 'vv'][u])) + oc.SetParameterString('oc', oc_fn) + oc.ExecuteAndWriteOutput() + for t in proc_idx[u::2]: + smooth = otb.Registry.CreateApplication('Smoothing') + smooth.SetParameterInputImage('in', self.pipe[t].GetParameterOutputImage(self.out_p[t])) + smooth.SetParameterString('type', 'mean') + smooth.SetParameterString('type.mean.radius', str(win_size)) + smooth.Execute() + self.append(smooth) + bm = otb.Registry.CreateApplication('BandMath') + if not outcore_on_disk: + bm.AddImageToParameterInputImageList('il', self.pipe[oc_idx].GetParameterOutputImage('oc')) + else: + bm.SetParameterStringList('il', [oc_fn]) + bm.AddImageToParameterInputImageList('il', self.pipe[-1].GetParameterOutputImage('out')) + bm.SetParameterString('exp', 'im2b1*im1b1/im1b2') + bm.Execute() + tmp_fns.append(self.files[t].replace('.tiff', '_filt.tiff')) + tmp_pipe.append(bm) + + N = int(len(tmp_pipe) / 2) + for i in range(N): + self.append(tmp_pipe[i], tmp_fns[i], ty, 'out', is_output=True) + self.append(tmp_pipe[N + i], tmp_fns[N + i], ty, 'out', is_output=True) + + def compute_features(self, feat_list=['VH_db', 'VV_db']): + proc_idx = self.out_idx.copy() + self.out_idx = [] + for k in range(0,len(proc_idx),2): + bm = otb.Registry.CreateApplication('BandMathX') + bm.AddImageToParameterInputImageList('il', self.pipe[proc_idx[k]].GetParameterOutputImage( + self.out_p[proc_idx[k]])) + bm.AddImageToParameterInputImageList('il', self.pipe[proc_idx[k+1]].GetParameterOutputImage( + self.out_p[proc_idx[k+1]])) + expr = [] + for f in feat_list: + expr.append(self.FEAT_exp[f]) + expr = '{' + ';'.join(expr) + '}' + bm.SetParameterString('exp', expr) + bm.Execute() + fn = self.files[proc_idx[k]].replace('-vh-', '-feat-') + if all(['db' in x for x in feat_list]): + ty = self.VAL_TYPE + else: + ty = self.TMP_TYPE + self.append(bm, fn, ty, 'out', is_output=True) + + + def write_outputs(self, fld, update_pipe=False, compress=False): + out = [] + proc_idx = self.out_idx.copy() + if update_pipe: + self.out_idx = [] + for t in proc_idx: + er = otb.Registry.CreateApplication('ExtractROI') + er.SetParameterInputImage('in', self.pipe[t].GetParameterOutputImage(self.out_p[t])) + er.SetParameterString('mode', 'fit') + er.SetParameterString('mode.fit.im', self.roi) + out_file = os.path.join(fld, self.files[t]) + if compress: + out_file += '?gdal:co:compress=deflate' + if not os.path.exists(os.path.dirname(out_file)): + os.makedirs(os.path.dirname(out_file)) + er.SetParameterString('out', out_file) + er.SetParameterOutputImagePixelType('out', self.types[t]) + er.ExecuteAndWriteOutput() + out.append(out_file) + if update_pipe: + self.append(to_otb_pipeline(out_file), self.files[t], self.types[t], 'out', is_output=True) + return out + + + + + + diff --git a/TimeSeries/s2sen2cor.py b/TimeSeries/s2sen2cor.py new file mode 100644 index 0000000000000000000000000000000000000000..01de7aca13e6ac0886626b55b0d32315e97b3a86 --- /dev/null +++ b/TimeSeries/s2sen2cor.py @@ -0,0 +1,173 @@ +import gdal +import otbApplication as otb +from otb_numpy_proc import to_otb_pipeline +import glob +import os +import sys +import zipfile +import osr +import datetime +from s2theia import * + +class S2Sen2CorTilePipeline(S2TheiaTilePipeline): + # --- BEGIN SENSOR PROTOTYPE --- + + NAME = 'S2-SEN2COR' + REF_TYPE = otb.ImagePixelType_uint16 + MSK_TYPE = otb.ImagePixelType_uint8 + PTRN_dir = 'S2*_MSIL2A*' + PTRN_ref = '_B' + B2_name = 'B02' + PTRN_10m = ['GRANULE/*/IMG_DATA/R10m/*_B02_10m.jp2', + 'GRANULE/*/IMG_DATA/R10m/*_B03_10m.jp2', + 'GRANULE/*/IMG_DATA/R10m/*_B04_10m.jp2', + 'GRANULE/*/IMG_DATA/R10m/*_B08_10m.jp2'] + PTRN_20m = ['GRANULE/*/IMG_DATA/R20m/*_B05_20m.jp2', + 'GRANULE/*/IMG_DATA/R20m/*_B06_20m.jp2', + 'GRANULE/*/IMG_DATA/R20m/*_B07_20m.jp2', + 'GRANULE/*/IMG_DATA/R20m/*_B8A_20m.jp2', + 'GRANULE/*/IMG_DATA/R20m/*_B11_20m.jp2', + 'GRANULE/*/IMG_DATA/R20m/*_B12_20m.jp2'] + PTRN_msk = ['GRANULE/*/IMG_DATA/R20m/*_SCL_20m.jp2'] + PTRN_ful = PTRN_10m[0:3] + PTRN_20m[0:3] + [PTRN_10m[3]] + PTRN_20m[3:] + FEAT_exp = { + 'B2': 'im1b1', + 'B3': 'im1b2', + 'B4': 'im1b3', + 'B5': 'im1b4', + 'B6': 'im1b5', + 'B7': 'im1b6', + 'B8': 'im1b7', + 'B8A': 'im1b8', + 'B11': 'im1b9', + 'B12': 'im1b10', + 'NDVI': '(im1b7-im1b3)/(im1b7+im1b3+1e-6)', + 'NDWI': '(im1b2-im1b7)/(im1b2+im1b7+1e-6)', + 'BRI': 'sqrt(' + '+'.join(['im1b%d*im1b%d' % (i, i) for i in range(1, 11)]) + ')', + 'MNDWI': '(im1b2-im1b9)/(im1b2+im1b9+1e-6)', + 'SWNDVI': '(im1b9-im1b7)/(im1b9+im1b7+1e-6)', + 'NDRE': '(im1b7-im1b4)/(im1b7+im1b4+1e-6)' + } + NDT = 0 + + @classmethod + def _check(cls,x): + return cls.PTRN_dir.split('_')[-1].replace('*', '') in os.path.basename(x) + + @classmethod + def _img_id(cls,x): + if cls._check(x): + if os.path.isdir(x) or os.path.splitext(x)[-1] == '.zip': + return '_'.join(os.path.splitext(x)[0].split('_')[-5:]) + else: + return None + else: + return None + + @classmethod + def _img_date(cls,x): + if cls._check(x): + if os.path.isdir(x) or os.path.splitext(x)[-1] == '.zip': + return os.path.splitext(x)[0].split('_')[-5].split('T')[0] + else: + return None + else: + return None + + @classmethod + def _tile_id(cls,x): + if cls._check(x): + if os.path.isdir(x) or os.path.splitext(x)[-1] == '.zip': + return os.path.splitext(x)[0].split('_')[-2] + else: + return None + else: + return None + + @classmethod + def _tile_cloud_percentage(cls, x): + if cls._check(x): + if os.path.isdir(x): + fid = open(glob.glob(os.path.join(x, '*/*/MTD_TL.xml'))[0], 'r') + mtd = fid.read() + elif os.path.splitext(x)[-1] == '.zip': + arch = zipfile.ZipFile(x) + fid = [name for name in arch.namelist() if name.endswith('MTD_TL.xml')] + mtd = arch.read(fid[0]) + root = et.fromstring(mtd) + f = filter(lambda x: x.tag == "CLOUDY_PIXEL_PERCENTAGE", root.findall('*/*/*')) + r = list(f) + return float(r[0].text) + + """ + def _process_mask(self, msks): + msk_pipe = [otb.Registry.CreateApplication('Superimpose')] + msk_pipe[-1].SetParameterInputImage('inr', self.pipe[-1].GetParameterOutputImage('out')) + msk_pipe[-1].SetParameterInputImage('inm', msks[0].GetParameterOutputImage('out')) + msk_pipe[-1].SetParameterString('interpolator', 'nn') + msk_pipe[-1].Execute() + msk_pipe.append(otb.Registry.CreateApplication('BandMath')) + msk_pipe[-1].AddImageToParameterInputImageList('il', msk_pipe[-2].GetParameterOutputImage('out')) + msk_pipe[-1].SetParameterString('exp', 'im1b1==0 || im1b1==1 || im1b1==2 || im1b1==7 ||im1b1==3 || im1b1==8 || im1b1==9 || im1b1==10') + msk_pipe[-1].Execute() + msk_pipe.append(otb.Registry.CreateApplication('BinaryMorphologicalOperation')) + msk_pipe[-1].SetParameterInputImage('in', msk_pipe[-2].GetParameterOutputImage('out')) + msk_pipe[-1].SetParameterString('filter', 'erode') + msk_pipe[-1].SetParameterString('structype', 'ball') + msk_pipe[-1].SetParameterString('xradius', '5') + msk_pipe[-1].SetParameterString('yradius', '5') + msk_pipe[-1].Execute() + msk_pipe.append(otb.Registry.CreateApplication('BinaryMorphologicalOperation')) + msk_pipe[-1].SetParameterInputImage('in', msk_pipe[-2].GetParameterOutputImage('out')) + msk_pipe[-1].SetParameterString('filter', 'dilate') + msk_pipe[-1].SetParameterString('structype', 'ball') + msk_pipe[-1].SetParameterString('xradius', '20') + msk_pipe[-1].SetParameterString('yradius', '20') + msk_pipe[-1].Execute() + return msk_pipe + """ + + def _process_mask(self, msks): + msk_pipe = [otb.Registry.CreateApplication('BandMath')] + msk_pipe[-1].AddImageToParameterInputImageList('il', msks[0].GetParameterOutputImage('out')) + # excluding thin cirrus from masking : no im1b1 == 10 below + # excluding cloud shadows here (for opening) : no im1b1 == 3 below + msk_pipe[-1].SetParameterString('exp', 'im1b1==0 || im1b1==1 || im1b1==8 || im1b1==9') + msk_pipe[-1].Execute() + msk_pipe.append(otb.Registry.CreateApplication('BandMath')) + msk_pipe[-1].AddImageToParameterInputImageList('il', msks[0].GetParameterOutputImage('out')) + msk_pipe[-1].SetParameterString('exp', 'im1b1==2 || im1b1 == 3 || im1b1==7') + msk_pipe[-1].Execute() + msk_pipe.append(otb.Registry.CreateApplication('BinaryMorphologicalOperation')) + msk_pipe[-1].SetParameterInputImage('in', msk_pipe[-2].GetParameterOutputImage('out')) + msk_pipe[-1].SetParameterString('filter', 'opening') + msk_pipe[-1].SetParameterString('structype', 'ball') + msk_pipe[-1].SetParameterString('xradius', '5') + msk_pipe[-1].SetParameterString('yradius', '5') + msk_pipe[-1].Execute() + msk_pipe.append(otb.Registry.CreateApplication('BandMath')) + msk_pipe[-1].AddImageToParameterInputImageList('il', msk_pipe[-4].GetParameterOutputImage('out')) + msk_pipe[-1].AddImageToParameterInputImageList('il', msk_pipe[-2].GetParameterOutputImage('out')) + msk_pipe[-1].SetParameterString('exp', 'im1b1 || im2b1') + msk_pipe[-1].Execute() + msk_pipe.append(otb.Registry.CreateApplication('BinaryMorphologicalOperation')) + msk_pipe[-1].SetParameterInputImage('in', msk_pipe[-2].GetParameterOutputImage('out')) + msk_pipe[-1].SetParameterString('filter', 'dilate') + msk_pipe[-1].SetParameterString('structype', 'ball') + msk_pipe[-1].SetParameterString('xradius', '20') + msk_pipe[-1].SetParameterString('yradius', '20') + msk_pipe[-1].Execute() + msk_pipe.append(otb.Registry.CreateApplication('Superimpose')) + msk_pipe[-1].SetParameterInputImage('inr', self.pipe[-1].GetParameterOutputImage('out')) + msk_pipe[-1].SetParameterInputImage('inm', msk_pipe[-2].GetParameterOutputImage('out')) + msk_pipe[-1].SetParameterString('interpolator', 'nn') + msk_pipe[-1].Execute() + return msk_pipe + + # ---- END SENSOR PROTOTYPE ---- + +class S2Sen2CorPipeline(S2TheiaPipeline): + + S2TilePipeline = S2Sen2CorTilePipeline + _check = S2TilePipeline._check + _tile_id = S2TilePipeline._tile_id diff --git a/TimeSeries/s2theia.py b/TimeSeries/s2theia.py new file mode 100644 index 0000000000000000000000000000000000000000..9d2b7d940b63e89d237ecca5bb4074fc101f478c --- /dev/null +++ b/TimeSeries/s2theia.py @@ -0,0 +1,810 @@ +import sys +import warnings + +import gdal +import otbApplication as otb + +from otb_numpy_proc import to_otb_pipeline +import numpy as np +import glob +import os +import xml.etree.ElementTree as et +import zipfile +import osr +import datetime +import uuid +import shutil + +class S2TheiaTilePipeline: + # --- BEGIN SENSOR PROTOTYPE --- + + NAME = 'S2-THEIA' + REF_TYPE = otb.ImagePixelType_int16 + MSK_TYPE = otb.ImagePixelType_uint8 + PTRN_dir = 'SENTINEL2*' + PTRN_ref = '_FRE_' + B2_name = 'B2' + PTRN_10m = ['*_FRE_B2.tif', '*_FRE_B3.tif', '*_FRE_B4.tif', '*_FRE_B8.tif'] + PTRN_20m = ['*_FRE_B5.tif', '*_FRE_B6.tif', '*_FRE_B7.tif', '*_FRE_B8A.tif', '*_FRE_B11.tif', '*_FRE_B12.tif'] + PTRN_msk = ['MASKS/*_EDG_R1.tif', 'MASKS/*_SAT_R1.tif', 'MASKS/*_CLM_R1.tif'] + PTRN_ful = PTRN_10m[0:3] + PTRN_20m[0:3] + [PTRN_10m[3]] + PTRN_20m[3:] + FEAT_exp = { + 'B2': 'im1b1', + 'B3': 'im1b2', + 'B4': 'im1b3', + 'B5': 'im1b4', + 'B6': 'im1b5', + 'B7': 'im1b6', + 'B8': 'im1b7', + 'B8A': 'im1b8', + 'B11': 'im1b9', + 'B12': 'im1b10', + 'NDVI': '(im1b7-im1b3)/(im1b7+im1b3+1e-6)', + 'NDWI': '(im1b2-im1b7)/(im1b2+im1b7+1e-6)', + 'BRI': 'sqrt(' + '+'.join(['im1b%d*im1b%d' % (i, i) for i in range(1, 11)]) + ')', + 'MNDWI': '(im1b2-im1b9)/(im1b2+im1b9+1e-6)', + 'SWNDVI': '(im1b9-im1b7)/(im1b9+im1b7+1e-6)', + 'NDRE': '(im1b7-im1b4)/(im1b7+im1b4+1e-6)' + } + NDT = -10000 + + @classmethod + def _check(cls,x): + return cls.PTRN_dir.replace('*', '') in os.path.basename(x) + + @classmethod + def _img_id(cls,x): + if cls._check(x): + if os.path.isdir(x): + return x.split('_')[-5] + elif os.path.splitext(x)[-1] == '.zip': + return x.split('_')[-4] + else: + return None + else: + return None + + @classmethod + def _img_date(cls,x): + if cls._check(x): + if os.path.isdir(x): + return x.split('_')[-5].split('-')[0] + elif os.path.splitext(x)[-1] == '.zip': + return x.split('_')[-4].split('-')[0] + else: + return None + else: + return None + + @classmethod + def _tile_id(cls,x): + if cls._check(x): + if os.path.isdir(x): + return x.split('_')[-3] + elif os.path.splitext(x)[-1] == '.zip': + return x.split('_')[-2] + else: + return None + else: + return None + + @classmethod + def _tile_cloud_percentage(cls, x): + if cls._check(x): + if os.path.isdir(x): + fid = open(glob.glob(os.path.join(x, '*_MTD_ALL.xml'))[0], 'r') + mtd = fid.read() + elif os.path.splitext(x)[-1] == '.zip': + arch = zipfile.ZipFile(x) + fid = [name for name in arch.namelist() if name.endswith('_MTD_ALL.xml')] + mtd = arch.read(fid[0]) + root = et.fromstring(mtd) + f = filter(lambda x: x.get('name') == 'CloudPercent', root.findall('*/*/*/*/*/*')) + r = list(f) + return float(r[0].text) + + @classmethod + def _check_roi(cls, x, roi, min_surf=0.0, temp_fld='/tmp'): + if cls._check(x): + if os.path.isdir(x): + er = otb.Registry.CreateApplication('ExtractROI') + bnd = glob.glob(os.path.join(x, cls.PTRN_20m[0]))[0] + elif os.path.splitext(x)[-1] == '.zip': + idf = cls.PTRN_20m[0].replace('*', '') + arch = zipfile.ZipFile(x) + fid = [name for name in arch.namelist() if idf in name] + fle = arch.read(fid[0]) + bnd = os.path.join(temp_fld, os.path.basename(fid[0])) + tgt = open(bnd, 'wb') + with fle,tgt: + shutil.copyfileobj(fle, tgt) + er.SetParameterString('in', bnd) + er.SetParameterString('mode', 'fit') + er.SetParameterString('mode.fit.vect', roi) + er.Execute() + arr = er.GetImageAsNumpyArray('out') + if (np.sum(arr != cls.NDT) / (arr.shape[0]*arr.shape[1])) <= min_surf: + return False + else: + return True + + def _process_mask(self, msks): + msk_pipe = [otb.Registry.CreateApplication('BandMath')] + [msk_pipe[-1].AddImageToParameterInputImageList('il', x.GetParameterOutputImage('out')) for x in + msks] + msk_pipe[-1].SetParameterString('exp', 'im1b1!=0 || im2b1!=0 || im3b1!=0') + msk_pipe[-1].Execute() + return msk_pipe + + # ---- END SENSOR PROTOTYPE ---- + + def __init__(self, fld, tile, temp_fld='/tmp', input_date_interval=None, max_clouds_percentage=None, + filter_by_roi=None, roi_min_surf=0.0, dummy_read=False): + self.pipe = [] + self.files = [] + self.types = [] + self.out_p = [] + self.out_idx = [] + + self.id = str(uuid.uuid4()) + self.folder = os.path.abspath(fld) + self.tile_id = tile + self.temp_fld = temp_fld + os.sep + self.id + self.image_list = self.parse_folder(self.folder, self.tile_id) + + if len(self.image_list) > 0: + self.tile_id = self._tile_id(self.image_list[0]) + self.input_dates = [self._img_date(x) for x in self.image_list] + self.tile_cloud_percentage = [] + + if input_date_interval is not None: + idx = [i for i in range(len(self.input_dates)) if self.input_dates[i]>=input_date_interval[0] and self.input_dates[i]<=input_date_interval[1]] + self.image_list = [self.image_list[i] for i in idx] + self.input_dates = [self.input_dates[i] for i in idx] + + if not dummy_read: + + if filter_by_roi is not None: + assert(os.path.exists(filter_by_roi)) + idx = [i for i in range(len(self.input_dates)) if self._check_roi(self.image_list[i], filter_by_roi, roi_min_surf)] + self.image_list = [self.image_list[i] for i in idx] + self.input_dates = [self.input_dates[i] for i in idx] + + if max_clouds_percentage is not None: + self.tile_cloud_percentage = [self._tile_cloud_percentage(x) for x in self.image_list] + idx = [i for i in range(len(self.input_dates)) if self.tile_cloud_percentage[i] <= max_clouds_percentage] + self.image_list = [self.image_list[i] for i in idx] + self.input_dates = [self.input_dates[i] for i in idx] + self.tile_cloud_percentage = [self.tile_cloud_percentage[i] for i in idx] + + self.set_input_epsg() + self.output_epsg = self.input_epsg + self.output_dates = self.input_dates + + for img in self.image_list: + for p in self.PTRN_ful: + ifn, ofn = self.get_file(img, p) + self.append(to_otb_pipeline(ifn), ofn, self.REF_TYPE, 'out', is_output=True) + for p in self.PTRN_msk: + ifn, ofn = self.get_file(img, p) + self.append(to_otb_pipeline(ifn), ofn, self.MSK_TYPE, 'out', is_output=True) + + else: + warnings.warn('Empty pipeline. Need to set preprocessed inputs?') + + def __del__(self): + if os.path.exists(self.temp_fld): + shutil.rmtree(self.temp_fld) + + def reset(self): + self.pipe = [] + self.files = [] + self.types = [] + self.out_p = [] + self.out_idx = [] + + for img in self.image_list: + for p in self.PTRN_ful: + ifn, ofn = self.get_file(img, p) + self.append(to_otb_pipeline(ifn), ofn, self.REF_TYPE, 'out', is_output=True) + for p in self.PTRN_msk: + ifn, ofn = self.get_file(img, p) + self.append(to_otb_pipeline(ifn), ofn, self.MSK_TYPE, 'out', is_output=True) + + def get_file(self, img, ptrn): + in_fn, out_fn = None, None + if os.path.isdir(img): + in_fn = glob.glob(os.path.join(img, ptrn))[0] + out_fn = in_fn.replace(self.folder, '').lstrip(os.sep) + elif os.path.splitext(img)[-1] == '.zip': + z = zipfile.ZipFile(img) + out_fn = [x for x in z.namelist() if ptrn.split(os.sep)[-1].replace('*','') in x][0] + in_fn = os.sep + 'vsizip' + os.sep + os.path.abspath(img) + os.sep + out_fn + return in_fn, os.path.splitext(self.tile_id + os.sep + out_fn)[0] + '.tif' + + def parse_folder(self, fld, tile): + img_list = [os.path.abspath(x) for x in glob.glob(os.path.join(fld, self.PTRN_dir)) + if os.path.isdir(x) and self._tile_id(x) == tile] + zip_list = [os.path.abspath(x) for x in glob.glob(os.path.join(fld, self.PTRN_dir)) + if os.path.splitext(x)[-1] == '.zip' and self._tile_id(x) == tile] + im_dict = {} + for i in img_list: + im_dict[self._img_id(i)] = i + for i in zip_list: + if self._img_id(i) not in im_dict.keys(): + im_dict[self._img_id(i)] = i + return sorted(im_dict.values(), key=lambda x: self._img_id(x)) + + def get_coverage(self): + proc_idx = self.out_idx.copy() + proc_idx = proc_idx[1::2] + cc = [] + for t in proc_idx: + arr = self.pipe[t].GetImageAsNumpyArray('out') + cc.append(np.sum(arr)/(arr.shape[0]*arr.shape[1])) + return cc + + def set_input_epsg(self): + f, _ = self.get_file(self.image_list[0], self.PTRN_20m[0]) + ds = gdal.Open(f) + self.input_epsg = osr.SpatialReference(wkt=ds.GetProjection()).GetAuthorityCode('PROJCS') + ds = None + + def append(self, app, fname=None, ftype=None, outp=None, is_output=False): + if is_output: + self.out_idx.append(len(self.pipe)) + self.pipe.append(app) + self.files.append(fname) + self.types.append(ftype) + self.out_p.append(outp) + + def clip(self, roi): + assert(os.path.exists(roi)) + proc_idx = self.out_idx.copy() + self.out_idx = [] + for t in proc_idx: + er = otb.Registry.CreateApplication('ExtractROI') + er.SetParameterInputImage('in', self.pipe[t].GetParameterOutputImage(self.out_p[t])) + er.SetParameterString('mode', 'fit') + er.SetParameterString('mode.fit.vect', roi) + er.Execute() + fn = self.files[t] + ty = self.types[t] + self.append(er, fn, ty, 'out', is_output=True) + + def preprocess(self): + proc_idx = self.out_idx.copy() + self.out_idx = [] + num_of_mask_inputs = len(self.PTRN_msk) + for t in proc_idx[::10+num_of_mask_inputs]: + idx_to_stack = [] + for k in range(t,t+10): + if k % (10 + num_of_mask_inputs) in [3, 4, 5, 7, 8, 9]: + si = otb.Registry.CreateApplication('Superimpose') + si.SetParameterInputImage('inm', self.pipe[k].GetParameterOutputImage('out')) + si.SetParameterInputImage('inr', self.pipe[t].GetParameterOutputImage('out')) + si.Execute() + self.append(si) + cr = otb.Registry.CreateApplication('BandMath') + cr.AddImageToParameterInputImageList('il', self.pipe[-1].GetParameterOutputImage('out')) + cr.SetParameterString('exp', '(im1b1 < 0 && im1b1 !=' + str(self.NDT) + ') ? 0 : im1b1') + cr.Execute() + self.append(cr) + idx_to_stack.append(len(self.pipe)-1) + else: + idx_to_stack.append(k) + + cct = otb.Registry.CreateApplication('ConcatenateImages') + [cct.AddImageToParameterInputImageList('il', self.pipe[j].GetParameterOutputImage('out')) for j in idx_to_stack] + cct.Execute() + fn = self.files[t].replace(self.B2_name, 'STACK') + ty = self.REF_TYPE + self.append(cct, fn, ty, 'out', is_output=True) + + msks = self.pipe[t+10:t+10+num_of_mask_inputs] + msk_pipe = self._process_mask(msks) + for app in msk_pipe[:-1]: + self.append(app) + fn = self.files[t].replace(self.B2_name, 'BINARY_MASK') + ty = self.MSK_TYPE + self.append(msk_pipe[-1], fn, ty, 'out', is_output=True) + + def write_src_quicklooks(self, fld, bnds = [3,2,1], scale_factor=0.2): + proc_idx = self.out_idx.copy() + fns = [fld + os.sep + self.NAME + '_' + self._img_id(x) + '.png' for x in self.image_list] + + for t,m in zip(proc_idx[::2],proc_idx[1::2]): + rt = otb.Registry.CreateApplication('RigidTransformResample') + rt.SetParameterInputImage('in', self.pipe[t].GetParameterOutputImage('out')) + rt.SetParameterFloat('transform.type.id.scalex', scale_factor) + rt.SetParameterFloat('transform.type.id.scaley', scale_factor) + rt.Execute() + + rtm = otb.Registry.CreateApplication('RigidTransformResample') + rtm.SetParameterInputImage('in', self.pipe[m].GetParameterOutputImage('out')) + rtm.SetParameterFloat('transform.type.id.scalex', scale_factor) + rtm.SetParameterFloat('transform.type.id.scaley', scale_factor) + rtm.SetParameterString('interpolator', 'nn') + rtm.Execute() + + bmm = otb.Registry.CreateApplication('BandMath') + bmm.AddImageToParameterInputImageList('il', rtm.GetParameterOutputImage('out')) + bmm.SetParameterString('exp', '1 - im1b1') + if not os.path.exists(self.temp_fld): + os.makedirs(self.temp_fld) + bmm.SetParameterString('out', self.temp_fld + os.sep + 'msk.tif') + bmm.ExecuteAndWriteOutput() + + dc = otb.Registry.CreateApplication('DynamicConvert') + dc.SetParameterInputImage('in', rt.GetParameterOutputImage('out')) + dc.SetParameterString('mask', self.temp_fld + os.sep + 'msk.tif') + dc.SetParameterString('channels', 'rgb') + dc.SetParameterInt('channels.rgb.red', bnds[0]) + dc.SetParameterInt('channels.rgb.green', bnds[1]) + dc.SetParameterInt('channels.rgb.blue', bnds[2]) + dc.SetParameterString('quantile.high', '2') + dc.SetParameterString('quantile.low', '2') + dc.SetParameterString('outmin', '1') + dc.SetParameterString('outmax', '255') + + dc.SetParameterString('out', fns.pop(0)) + dc.SetParameterOutputImagePixelType('out', otb.ImagePixelType_uint8) + dc.ExecuteAndWriteOutput() + + def reproject(self, epsg): + if epsg != self.input_epsg: + self.output_epsg = epsg + proc_idx = self.out_idx.copy() + self.out_idx = [] + i = 0 + for t in proc_idx: + rp = otb.Registry.CreateApplication('OrthoRectification') + rp.SetParameterInputImage('io.in', self.pipe[t].GetParameterOutputImage('out')) + rp.SetParameterString('map', 'epsg') + rp.SetParameterString('map.epsg.code', self.output_epsg) + rp.SetParameterString('opt.gridspacing', '40') + if i % 2 == 0: + rp.SetParameterString('outputs.default', str(self.NDT)) + fn = self.files[t].replace('_STACK.tif', '_STACK_' + self.output_epsg + '.tif') + ty = self.REF_TYPE + else: + rp.SetParameterString('interpolator', 'nn') + fn = self.files[t].replace('_BINARY_MASK.tif', '_BINARY_MASK_' + self.output_epsg + '.tif') + ty = self.MSK_TYPE + rp.Execute() + self.append(rp, fn, ty, 'io.out', is_output=True) + i += 1 + + def coregister(self, ref_img, ref_bnd, tgt_bnd, out_fld=None): + # PointMatchCoregistration is a pipeline-breaking app + # Need to write outputs, compute coregistered and write again + # Then update pipeline + to_del = self.write_outputs(self.temp_fld, update_pipe=True) + proc_idx = self.out_idx.copy() + self.out_idx = [] + i = 0 + curr_json = '' + for t in proc_idx: + cr = otb.Registry.CreateApplication('PointMatchCoregistration') + cr.SetParameterInputImage('in', self.pipe[t].GetParameterOutputImage('out')) + cr.SetParameterInt('fallback', 1) + if (i % 2) == 0: + curr_json = os.path.splitext(self.files[t])[0] + '.json' + curr_json = os.path.join(self.temp_fld, curr_json) + cr.SetParameterInt('band', tgt_bnd) + cr.SetParameterString('inref', ref_img) + cr.SetParameterInt('bandref', ref_bnd) + cr.SetParameterString('outjson', curr_json) + fn = self.files[t].replace('_STACK.tif', '_STACK_COREG.tif') + ty = self.REF_TYPE + else: + cr.SetParameterString('inmodel', curr_json) + cr.SetParameterString('interpolator', 'nn') + fn = self.files[t].replace('_BINARY_MASK.tif', '_BINARY_MASK_COREG.tif') + ty = self.MSK_TYPE + self.append(cr, fn, ty, 'out', is_output=True) + i += 1 + if out_fld is None: + out_fld = self.temp_fld + self.write_outputs(out_fld, update_pipe=True, flag_nodata=True) + + def parse_dates(self, fn): + with open(fn) as f: + return [l for l in f.read().splitlines() if l] + + def gapfill(self, output_dates=None, on_disk=False): + #assert(os.path.exists(output_dates)) + proc_idx = self.out_idx.copy() + self.out_idx = [] + + stk = otb.Registry.CreateApplication('ConcatenateImages') + [stk.AddImageToParameterInputImageList('il', self.pipe[x].GetParameterOutputImage(self.out_p[x])) for x in proc_idx[::2]] + stk.Execute() + self.append(stk) + + mst = otb.Registry.CreateApplication('ConcatenateImages') + [mst.AddImageToParameterInputImageList('il', self.pipe[x].GetParameterOutputImage(self.out_p[x])) for x in proc_idx[1::2]] + mst.Execute() + self.append(mst) + + with open(os.path.join(self.folder, self.tile_id + '_indates.txt'), 'w') as df: + [df.write(x + '\n') for x in self.input_dates] + + if output_dates is not None: + od = self.parse_dates(output_dates) + self.output_dates = od + else: + output_dates = os.path.join(self.folder, self.tile_id + '_indates.txt') + od = self.input_dates + + self.output_dates = od + + gf = otb.Registry.CreateApplication('ImageTimeSeriesGapFilling') + gf.SetParameterInputImage('in', self.pipe[-2].GetParameterOutputImage('out')) + gf.SetParameterInputImage('mask', self.pipe[-1].GetParameterOutputImage('out')) + gf.SetParameterInt('comp', 10) + gf.SetParameterString('it', 'linear') + gf.SetParameterString('id', os.path.join(self.folder, self.tile_id + '_indates.txt')) + gf.SetParameterString('od', output_dates) + if not on_disk: + gf.Execute() + gf_out = gf + else: + gf_fn = self.temp_fld + os.sep + 'SENTINEL2_' + self.tile_id + '_GAPFILLED_FULL.tif' + if not os.path.exists(gf_fn): + if not os.path.exists(self.temp_fld): + os.makedirs(self.temp_fld) + gf.SetParameterString("out", gf_fn) + gf.ExecuteAndWriteOutput() + gf_out = to_otb_pipeline(gf_fn) + self.append(gf_out) + + t = 1 + for d in od: + ch_list = ['Channel%d' % i for i in range(t,t+10)] + t += 10 + er = otb.Registry.CreateApplication('ExtractROI') + er.SetParameterInputImage('in', gf_out.GetParameterOutputImage('out')) + er.UpdateParameters() + er.SetParameterStringList('cl', ch_list) + er.Execute() + dn = 'SENTINEL2_' + self.tile_id + '_GAPFILL_' + d + fn = self.tile_id + os.sep + dn + os.sep + dn + '_STACK.tif' + ty = self.REF_TYPE + self.append(er, fn, ty, 'out', is_output=True) + + def generate_feature_stack(self, feat_list=None): + proc_idx = self.out_idx.copy() + self.out_idx = [] + + exp_list = self.FEAT_exp.values() + stack_name = 'FEAT' + if feat_list is not None: + exp_list = [self.FEAT_exp[x] for x in feat_list] + stack_name = '_'.join(feat_list) + + for t in proc_idx: + bm = otb.Registry.CreateApplication('BandMathX') + bm.AddImageToParameterInputImageList('il', self.pipe[t].GetParameterOutputImage('out')) + bm.SetParameterString('exp', '{' + ';'.join(exp_list) + '}') + bm.Execute() + fn = self.files[t].replace('_STACK.tif', '_' + stack_name + '.tif') + ty = otb.ImagePixelType_float + self.append(bm, fn, ty, 'out', is_output=True) + + return stack_name + + def generate_time_series(self, feat_list): + proc_idx = self.out_idx.copy() + self.out_idx = [] + + for feat in feat_list: + bm = otb.Registry.CreateApplication('BandMathX') + i = 1 + expr = [] + for t in proc_idx: + expr.append(self.FEAT_exp[feat].replace('im1', 'im%d' % i)) + bm.AddImageToParameterInputImageList('il', self.pipe[t].GetParameterOutputImage('out')) + i += 1 + bm.SetParameterString('exp', '{' + ';'.join(expr) + '}') + bm.Execute() + fn = self.tile_id + os.sep + 'SENTINEL2_' + self.tile_id + '_GAPFILL_' + feat + '.tif' + ty = otb.ImagePixelType_float + self.append(bm, fn, ty, 'out', is_output=True) + + def set_preprocess_output(self, fld): + lst = self.parse_folder(os.path.join(fld, self.tile_id), self.tile_id) + if len(lst) == len(self.image_list): + self.out_idx = [] + for img in lst: + f = glob.glob(os.path.join(img, '*STACK.tif'))[0] + self.append(to_otb_pipeline(f), f, self.REF_TYPE, 'out', is_output=True) + f = glob.glob(os.path.join(img, '*BINARY_MASK.tif'))[0] + self.append(to_otb_pipeline(f), f, self.MSK_TYPE, 'out', is_output=True) + else: + warnings.warn("No matching files found as preprocess output. No modification to pipeline.") + + def set_gapfilled_output(self, file_pattern): + lst = sorted(glob.glob(file_pattern)) + self.out_idx = [] + if len(lst) > 0: + for f in lst: + pf = os.path.join(self.tile_id, os.path.basename(f)) + self.append(to_otb_pipeline(f), pf, self.REF_TYPE, 'out', is_output=True) + else: + warnings.warn("No matching files found as preprocess output. No modification to pipeline.") + + def write_outputs(self, fld, roi=None, update_pipe=False, compress=False, flag_nodata=False): + out = [] + proc_idx = self.out_idx.copy() + if roi is not None: + out_idx_bck = self.out_idx.copy() + pipe_length = len(self.pipe) + self.clip(roi) + if update_pipe: + assert roi is None, 'Cannot set output files as pipe input over a ROI, use clip function instead.' + self.out_idx = [] + for t in proc_idx: + out_file = os.path.join(fld, self.files[t]) + if compress: + out_file += '?gdal:co:compress=deflate' + if not os.path.exists(os.path.dirname(out_file)): + os.makedirs(os.path.dirname(out_file)) + self.pipe[t].SetParameterString(self.out_p[t], out_file) + self.pipe[t].SetParameterOutputImagePixelType(self.out_p[t], self.types[t]) + self.pipe[t].ExecuteAndWriteOutput() + out.append(out_file) + if update_pipe: + self.append(to_otb_pipeline(out_file), self.files[t], self.types[t], 'out', is_output=True) + if roi is not None: + self.out_idx = out_idx_bck + self.pipe = self.pipe[:pipe_length] + self.files = self.files[:pipe_length] + self.types = self.types[:pipe_length] + self.out_p = self.out_p[:pipe_length] + + if flag_nodata: + if isinstance(flag_nodata, bool): + val = self.NDT + elif isinstance(flag_nodata, int) or isinstance(flag_nodata,float): + val = flag_nodata + for f in out: + ds = gdal.Open(f, 1) + for i in range(ds.RasterCount): + ds.GetRasterBand(i+1).SetNoDataValue(val) + ds = None + + return out + + +class S2TheiaPipeline: + + S2TilePipeline = S2TheiaTilePipeline + _check = S2TilePipeline._check + _tile_id = S2TilePipeline._tile_id + + def __init__(self, fld, temp_fld='/tmp', input_date_interval=None, max_clouds_percentage=None): + self.folder = fld + self.temp_fld = temp_fld + self.input_date_interval = input_date_interval + self.max_clouds_percentage = max_clouds_percentage + self.tile_list = set() + img_list = [os.path.abspath(x) for x in glob.glob(os.path.join(self.folder, self.S2TilePipeline.PTRN_dir)) + if os.path.isdir(x) or os.path.splitext(x)[-1] == '.zip'] + [self.tile_list.add(self._tile_id(x)) for x in img_list if self._check(x)] + self.tiles = [self.S2TilePipeline(fld, t, self.temp_fld, self.input_date_interval, self.max_clouds_percentage) for t in self.tile_list] + self.output_dates = None + self.roi = None + self.output_epsg = self.tiles[0].input_epsg + + def __del__(self): + for x in self.tiles: + del x + + def set_output_dates_by_file(self, od): + self.output_dates = od + + def set_output_dates(self, start, end, step=10): + start_date = datetime.datetime(int(start[0:4]), int(start[4:6]), int(start[6:8])) + end_date = datetime.datetime(int(end[0:4]), int(end[4:6]), int(end[6:8])) + d, st = start_date, datetime.timedelta(step) + tmstp = str(datetime.datetime.timestamp(datetime.datetime.now())).replace('.', '') + ofn = self.folder + os.sep + 's2ppl_' + tmstp + '_output_dates.txt' + with open(ofn, 'w') as f: + while d < end_date: + f.write(d.strftime('%Y%m%d')+'\n') + d += st + self.output_dates = ofn + + def set_roi(self, roi): + self.roi = roi + + def set_output_epsg(self, epsg): + self.output_epsg = epsg + + def extract_feature_set(self, out_fld, feat_list=None, mosaicking=None, store_gapfill=False): + out = [] + stack_name = '' + if self.output_dates is not None: + for t in self.tiles: + if self.roi is not None: + t.clip(self.roi) + t.preprocess() + t.reproject(self.output_epsg) + t.gapfill(self.output_dates, store_gapfill) + stack_name = t.generate_feature_stack(feat_list) + out.append(t.write_outputs(out_fld)) + t.reset() + if mosaicking == 'vrt': + out_mos = [] + vrtopt = gdal.BuildVRTOptions() + for i in range(len(self.tiles[0].output_dates)): + fn = out_fld + os.sep + 'SENTINEL2_MOSAIC_GAPFILL_' + self.tiles[0].output_dates[i] + '_' + stack_name + '.vrt' + to_mosaic = [x[i] for x in out] + gdal.BuildVRT(fn, to_mosaic, options=vrtopt) + out_mos.append(fn) + return out_mos + return out + + def extract_time_series(self, out_fld, feat_list, mosaicking=None, store_gapfill=False): + out = [] + if self.output_dates is not None: + for t in self.tiles: + if self.roi is not None: + t.clip(self.roi) + t.preprocess() + t.reproject(self.output_epsg) + t.gapfill(self.output_dates, store_gapfill) + t.generate_time_series(feat_list) + out.append(t.write_outputs(out_fld)) + t.reset() + if mosaicking == 'vrt': + out_mos = [] + vrtopt = gdal.BuildVRTOptions() + for i in range(len(feat_list)): + fn = out_fld + os.sep + 'SENTINEL2_MOSAIC_GAPFILL_' + feat_list[i] + '.vrt' + to_mosaic = [x[i] for x in out] + gdal.BuildVRT(fn, to_mosaic, options=vrtopt) + out_mos.append(fn) + return out_mos + return out + + +""" +def single_tile_clip(ppl : S2Pipeline, roi, write_output=False, s2out=None): + + if write_output and s2out is None: + sys.exit("Please provide output folder (s2out=) when write_output is true") + + if s2out is not None: + s2out = os.path.abspath(s2out) + + out_list = [] + for l in ppl.pipe: + cur_list = [] + for f in l: + er = otb.Registry.CreateApplication('ExtractROI') + er.SetParameterString('in', f) + er.SetParameterString('mode', 'fit') + er.SetParameterString('mode.fit.vect', roi) + if write_output: + dst_file = os.path.join(s2out, f.replace(ppl.s2_folder, '')) + if not os.path.exists(os.path.dirname(dst_file)): + os.makedirs(os.path.dirname(dst_file)) + typ = MSK_TYPE + if PTRN_ref in os.path.basename(dst_file): + typ = REF_TYPE + er.SetParameterString('out', dst_file) + er.SetParameterOutputImagePixelType('out', typ) + er.ExecuteAndWriteOutput() + cur_list.append(to_otb_pipeline(dst_file)) + else: + er.Execute() + cur_list.append(er) + out_list.append(cur_list) + + ppl.set_pipeline(out_list) + + return + +def single_tile_preprocess(ppl : S2Pipeline, write_output=False, output_file=None): + + ppl, dl = single_tile_scan(s2fld) + + begin_index = len(ppl) + + for d in ppl[:begin_index]: + for i in range(10): + if i in [3, 4, 5, 7, 8, 9]: + ppl.append(otb.Registry.CreateApplication('Superimpose')) + ppl[-1].SetParameterInputImage('inm', d[i].GetParameterOutputImage('out')) + ppl[-1].SetParameterInputImage('inr', d[0].GetParameterOutputImage('out')) + ppl[-1].Execute() + else: + ppl.append(d[i]) + + ppl.append(otb.Registry.CreateApplication('ConcatenateImages')) + [ppl[-1].AddImageToParameterInputImageList('il', x.GetParameterOutputImage('out')) for x in ppl[begin_index:-1]] + ppl[-1].Execute() + + stack_index = len(ppl) - 1 + + for d in ppl[:begin_index]: + ppl.append(otb.Registry.CreateApplication('BandMath')) + [ppl[-1].AddImageToParameterInputImageList('il', x.GetParameterOutputImage('out')) for x in d[10:]] + ppl[-1].SetParameterString('exp', 'im1b1!=0 || im2b1!=0 || im3b1!=0') + ppl[-1].Execute() + + ppl.append(otb.Registry.CreateApplication('ConcatenateImages')) + [ppl[-1].AddImageToParameterInputImageList('il', x.GetParameterOutputImage('out')) for x in ppl[stack_index+1:-1]] + ppl[-1].Execute() + + mask_index = len(ppl) - 1 + + with open(os.path.join(s2fld, 'indates.txt'), 'w') as df: + [df.write(x + '\n') for x in dl] + + ppl.append(otb.Registry.CreateApplication('ImageTimeSeriesGapFilling')) + ppl[-1].SetParameterInputImage('in', ppl[stack_index].GetParameterOutputImage('out')) + ppl[-1].SetParameterInputImage('mask', ppl[mask_index].GetParameterOutputImage('out')) + ppl[-1].SetParameterInt('comp', 10) + ppl[-1].SetParameterString('it', 'linear') + ppl[-1].SetParameterString('id', os.path.join(s2fld, 'indates.txt')) + ppl[-1].SetParameterString('od', output_dates) + + if write_output is False: + ppl[-1].Execute() + return ppl + else: + if output_file is None: + sys.exit("Please provide an output file name when write_output is True.") + ppl[-1].SetParameterString('out', output_file) + ppl[-1].SetParameterOutputImagePixelType('out', REF_TYPE) + ppl[-1].ExecuteAndWriteOutput() + return output_file + +def single_tile_gapfilling() + +def single_tile_features(gf_stack, output_dates, write_output=False, output_folder=None): + + ppl = to_otb_pipeline(gf_stack) + n_dates = int(ppl[-1].GetImageNbBands('out') / 10) + print(n_dates) + + with open(output_dates,'r') as df: + dates = df.read().splitlines() + + if n_dates != len(dates): + sys.exit('Number of dates in stack does not correspond to the one in the text file.') + + gf_index = len(ppl) - 1 + + # Extract dates from gapfilling stacks + for d in range(n_dates): + ppl.append(otb.Registry.CreateApplication('ExtractROI')) + ppl[-1].SetParameterInputImage('in', ppl[gf_index].GetParameterOutputImage('out')) + ppl[-1].UpdateParameters() + ppl[-1].SetParameterStringList('cl', ['Channel%d' % i for i in range(10*d+1, 10*d+11)]) + ppl[-1].Execute() + + out_files = [] + + # Compute per-date features + for d in range(n_dates): + ppl.append(otb.Registry.CreateApplication('BandMathX')) + ppl[-1].AddImageToParameterInputImageList('il', ppl[gf_index+1+d].GetParameterOutputImage('out')) + ppl[-1].SetParameterString('exp', '{' + ';'.join(FEAT_exp) + '}') + if write_output is True: + print(True) + else: + ppl[-1].Execute() + + if write_output is True: + return out_files, n_dates + else: + return ppl, n_dates + + +if __name__ == "__main__": + single_tile_clip(sys.argv[1],sys.argv[2],sys.argv[3]) +""" \ No newline at end of file diff --git a/VHR/vhrbase.py b/VHR/vhrbase.py new file mode 100644 index 0000000000000000000000000000000000000000..175bf091840d81a78a10ad69e3223661a97b4092 --- /dev/null +++ b/VHR/vhrbase.py @@ -0,0 +1,189 @@ +import otbApplication as otb +from otb_numpy_proc import to_otb_pipeline +import os +import glob +from pathlib import Path +import re +import ogr + +class SPOT67RasterPipeline: + # BEGIN SPOT6/7 VHR PROTOTYPE + MS_FOLDER_PATTERN = 'IMG_SPOT*_MS_*' + PAN_FOLDER_PATTERN = 'IMG_SPOT*_P_*' + TILE_PATTERN = '*_R*C*.TIF' + NDT = 0.0 + REF_TYPE = otb.ImagePixelType_uint16 + TOA_TYPE = otb.ImagePixelType_float + GRID_SPC = [6, 24] + RS_LABEL = 'SEN' + MS_LABEL = 'MS' + OPT_IN = '?&skipcarto=true' + + @classmethod + def _find_tiles(cls, x): + lst = sorted(glob.glob(os.path.join(x, cls.TILE_PATTERN))) + res = re.search('R[0-9]C[0-9]', lst[-1]).group() + return lst, int(res[1]), int(res[3]), res + + def __init__(self, pan_root, ms_root=None): + self.out_idx = [] + self.pipe = [] + self.files = [] + self.types = [] + self.out_p = [] + + self.ms_folder = None + self.pan_folder = None + self.pipe = [] + + if ms_root is None: + ms_root = pan_root + + for p in Path(pan_root).rglob(self.PAN_FOLDER_PATTERN): + if os.path.isdir(p): + self.pan_folder = str(p) + break + + for p in Path(ms_root).rglob(self.MS_FOLDER_PATTERN): + if os.path.isdir(p): + self.ms_folder = str(p) + break + + if self.ms_folder is None or self.pan_folder is None: + raise ValueError('No MS and/or PAN folder found') + + self.pan_list, r_pan, c_pan, res = self._find_tiles(self.pan_folder) + tile_p = otb.Registry.CreateApplication('TileFusion') + lst = [x + self.OPT_IN for x in self.pan_list] + tile_p.SetParameterStringList('il', lst) + tile_p.SetParameterInt('rows', r_pan) + tile_p.SetParameterInt('cols', c_pan) + tile_p.Execute() + fn = os.path.basename(self.pan_list[-1]).replace(res, 'FULL') + ty = self.REF_TYPE + self.append(tile_p, fn, ty, 'out', is_output=True) + + self.ms_list, r_ms, c_ms, res = self._find_tiles(self.ms_folder) + tile_ms = otb.Registry.CreateApplication('TileFusion') + lst = [x + self.OPT_IN for x in self.ms_list] + tile_ms.SetParameterStringList('il', lst) + tile_ms.SetParameterInt('rows', r_ms) + tile_ms.SetParameterInt('cols', c_ms) + tile_ms.Execute() + fn = os.path.basename(self.ms_list[-1]).replace(res, 'FULL') + ty = self.REF_TYPE + self.append(tile_ms, fn, ty, 'out', is_output=True) + + def to_toa(self, clamp=True): + proc_idx = self.out_idx.copy() + self.out_idx = [] + + for p in proc_idx: + fn = self.files[p].replace('_FULL', '_FULL_TOA') + toa = otb.Registry.CreateApplication('OpticalCalibration') + toa.SetParameterInputImage('in', self.pipe[p].GetParameterOutputImage(self.out_p[p])) + toa.SetParameterInt('clamp', int(clamp)) + toa.Execute() + self.append(toa) + bm = otb.Registry.CreateApplication('BandMathX') + bm.AddImageToParameterInputImageList('il', self.pipe[-1].GetParameterOutputImage('out')) + bm.SetParameterString('exp', '10000*im1') + bm.Execute() + ty = self.REF_TYPE + self.append(bm, fn, ty, 'out', is_output=True) + return + + def orthorectify(self, dem_fld, geoid = None): + assert (os.path.isdir(dem_fld)) + proc_idx = self.out_idx.copy() + self.out_idx = [] + gspc_idx = 0 + for t in proc_idx: + ortho = otb.Registry.CreateApplication('OrthoRectification') + ortho.SetParameterString('elev.dem', dem_fld) + if geoid is not None: + assert (os.path.exists(geoid)) + ortho.SetParameterString('elev.geoid', geoid) + ortho.SetParameterInputImage('io.in', self.pipe[t].GetParameterOutputImage(self.out_p[t])) + ortho.SetParameterInt('opt.gridspacing', self.GRID_SPC[gspc_idx]) + ortho.Execute() + fn = self.files[t].replace(self.RS_LABEL, 'ORTHO') + ty = self.REF_TYPE + self.append(ortho, fn, ty, 'io.out', is_output=True) + gspc_idx += 1 + + def pansharp(self): + proc_idx = self.out_idx.copy() + self.out_idx = [] + assert(len(proc_idx) == 2) + btps = otb.Registry.CreateApplication("BundleToPerfectSensor") + btps.SetParameterInputImage('inp', self.pipe[proc_idx[0]].GetParameterOutputImage(self.out_p[proc_idx[0]])) + btps.SetParameterInputImage('inxs', self.pipe[proc_idx[1]].GetParameterOutputImage(self.out_p[proc_idx[1]])) + btps.SetParameterString('method', 'bayes') + btps.Execute() + fn = self.files[proc_idx[1]].replace(self.MS_LABEL, 'PS') + ty = self.REF_TYPE + self.append(btps, fn, ty, 'out', is_output=True) + + def clip(self, roi, buffer=0): + assert(os.path.exists(roi)) + proc_idx = self.out_idx.copy() + self.out_idx = [] + for t in proc_idx: + er = otb.Registry.CreateApplication('ExtractROI') + er.SetParameterInputImage('in', self.pipe[t].GetParameterOutputImage(self.out_p[t])) + er.SetParameterString('mode', 'extent') + ds = ogr.Open(roi) + ly = ds.GetLayer(0) + extent = ly.GetExtent() + extent = (extent[0] - buffer, extent[1] + buffer, extent[2] - buffer, extent[3] + buffer) + if ly.GetSpatialRef().GetAuthorityCode('PROJCS') == '4326': + er.SetParameterString('mode.extent.unit', 'lonlat') + else: + er.SetParameterString('mode.extent.unit', 'phy') + er.SetParameterFloat('mode.extent.ulx', extent[0]) + er.SetParameterFloat('mode.extent.uly', extent[2]) + er.SetParameterFloat('mode.extent.lrx', extent[1]) + er.SetParameterFloat('mode.extent.lry', extent[3]) + ly, ds = None, None + er.Execute() + fn = self.files[t] + ty = self.types[t] + self.append(er, fn, ty, 'out', is_output=True) + + def append(self, app, fname=None, ftype=None, outp=None, is_output=False): + if is_output: + self.out_idx.append(len(self.pipe)) + self.pipe.append(app) + self.files.append(fname) + self.types.append(ftype) + self.out_p.append(outp) + + def write_outputs(self, fld, roi=None, update_pipe=False, compress=False): + out = [] + out_idx_bck = self.out_idx.copy() + if roi is not None: + pipe_length = len(self.pipe) + self.clip(roi) + if update_pipe: + assert roi is None, 'Cannot set output files as pipe input over a ROI.' + self.out_idx = [] + for t in out_idx_bck: + out_file = os.path.join(fld, self.files[t]) + if compress: + out_file += '?gdal:co:compress=deflate' + if not os.path.exists(os.path.dirname(out_file)): + os.makedirs(os.path.dirname(out_file)) + self.pipe[t].SetParameterString(self.out_p[t], out_file) + self.pipe[t].SetParameterOutputImagePixelType(self.out_p[t], self.types[t]) + self.pipe[t].ExecuteAndWriteOutput() + out.append(out_file) + if update_pipe: + self.append(to_otb_pipeline(out_file), self.files[t], self.types[t], 'out', is_output=True) + if roi is not None: + self.out_idx = out_idx_bck + self.pipe = self.pipe[:pipe_length] + self.files = self.files[:pipe_length] + self.types = self.types[:pipe_length] + self.out_p = self.out_p[:pipe_length] + return out \ No newline at end of file