diff --git a/Common/geometry.py b/Common/geometry.py
index c8ff1fb58e0111a57b1c2aaacfd27d98bc3343be..7eb8a5b374fb8bd78bad2238de6cd59e53dd0795 100644
--- a/Common/geometry.py
+++ b/Common/geometry.py
@@ -49,7 +49,8 @@ def local_shift(src, tgt):
 def compute_displacement(_src: otb.Application, _tgt: otb.Application,
                          src_band=2, tgt_band=2,
                          out_param_src='out', out_param_tgt='out',
-                         geobin_size=32, geobin_spacing=256, margin=32, n_proc=6):
+                         geobin_size=32, geobin_spacing=256, margin=32,
+                         filter=5, n_proc=6):
 
     sz = _tgt.GetImageSize(out_param_tgt)
     W,H = sz[0], sz[1]
@@ -78,6 +79,8 @@ def compute_displacement(_src: otb.Application, _tgt: otb.Application,
     with mp.Pool(n_proc) as p:
         for res in p.starmap(local_shift, args):
             shifts.extend(res)
+    shifts = np.concatenate(shifts)
+
     """
     for xx in args:
         yy = local_shift(xx[0],xx[1])
@@ -92,6 +95,9 @@ def compute_displacement(_src: otb.Application, _tgt: otb.Application,
     _tgt.PropagateRequestedRegion(out_param_tgt, reg)
 
     if len(shifts) > 0:
+        if filter > 0:
+            nrm = np.linalg.norm(shifts, axis=1)
+            shifts = shifts[nrm < filter]
         return np.mean(np.array(shifts), axis=0), len(shifts)
     else:
         return None, 0
@@ -101,7 +107,7 @@ def compute_displacement_with_masks(_src: otb.Application, _tgt: otb.Application
                                     src_band=2, tgt_band=2, num_geobins=16,
                                     out_param_src='out', out_param_tgt='out',
                                     out_param_src_msk='out', out_param_tgt_msk='out',
-                                    geobin_radius=16, margin=32, n_proc=6):
+                                    geobin_radius=16, margin=32, filter=5, n_proc=6):
 
     geobin_size = 2 * geobin_radius + 1
     mask = 1 - _src_msk.GetImageAsNumpyArray(out_param_src_msk).astype(bool)
@@ -150,6 +156,9 @@ def compute_displacement_with_masks(_src: otb.Application, _tgt: otb.Application
     _tgt.PropagateRequestedRegion(out_param_tgt, reg)
 
     if len(shifts) > 0:
+        if filter > 0:
+            nrm = np.linalg.norm(shifts, axis=1)
+            shifts = shifts[nrm < filter]
         return np.mean(np.array(shifts), axis=0), len(shifts)
     else:
         return None, 0
@@ -192,14 +201,15 @@ def get_descriptors(_src: otb.Application, src_band=2, out_param='out',
 
 def get_displacements_sequentially(_lst: List[otb.Application],
                                    band=2, out_param='out',
-                                   geobin_size=32, geobin_spacing=256, margin=32):
+                                   geobin_size=32, geobin_spacing=256, margin=32,
+                                   filter=5):
 
     shifts = []
     for i in tqdm(range(len(_lst)-1)):
         sh = compute_displacement(_lst[i],_lst[i+1], src_band=band, tgt_band=band,
                                            out_param_src=out_param, out_param_tgt=out_param,
                                            geobin_size=geobin_size, geobin_spacing=geobin_spacing,
-                                           margin=margin)
+                                           margin=margin, filter=filter)
         shifts = [s + sh[0] for s in shifts]
         shifts.append(sh[0])
     shifts.append(np.array([0,0]))
@@ -210,7 +220,7 @@ def get_displacements_sequentially(_lst: List[otb.Application],
 
 def get_displacements_bestref(_lst: List[otb.Application], _msk: List[otb.Application],
                               ref_idx, band=2, out_param='out',
-                              geobin_radius=16, num_geobins=16, margin=32,
+                              geobin_radius=16, num_geobins=16, margin=32, filter=5,
                               use_masks=True):
 
     '''
@@ -241,12 +251,12 @@ def get_displacements_bestref(_lst: List[otb.Application], _msk: List[otb.Applic
                                                      out_param_src=out_param, out_param_tgt=out_param,
                                                      out_param_src_msk=out_param, out_param_tgt_msk=out_param,
                                                      geobin_radius=geobin_radius, num_geobins=num_geobins,
-                                                     margin=margin)
+                                                     margin=margin, filter=filter)
             else:
                 sh = compute_displacement(_lst[i], _lst[idx], src_band=band, tgt_band=band,
                                           out_param_src=out_param, out_param_tgt=out_param,
                                           geobin_size=geobin_size, geobin_spacing=geobin_spacing,
-                                          margin=margin)
+                                          margin=margin, filter=filter)
             if sh[0] is not None:
                 shifts.append(sh[0])
         else:
diff --git a/VHR/vhrbase.py b/VHR/vhrbase.py
index e76398063e061ffb84ee26f2b08ffea96415eb77..857d318e9911dbfb00594574b4d468ae5c81c591 100644
--- a/VHR/vhrbase.py
+++ b/VHR/vhrbase.py
@@ -4,7 +4,9 @@ import os
 import glob
 from pathlib import Path
 import re
-from osgeo import ogr
+from osgeo import ogr, gdal
+from Common.geometry import compute_displacement
+from math import ceil, floor, sqrt
 
 class SPOT67RasterPipeline:
     # BEGIN SPOT6/7 VHR PROTOTYPE
@@ -74,6 +76,8 @@ class SPOT67RasterPipeline:
         ty = self.REF_TYPE
         self.append(tile_ms, fn, ty, 'out', is_output=True)
 
+        self.shift = None
+
     def to_toa(self, clamp=True):
         proc_idx = self.out_idx.copy()
         self.out_idx = []
@@ -125,18 +129,45 @@ class SPOT67RasterPipeline:
         ty = self.REF_TYPE
         self.append(btps, fn, ty, 'out', is_output=True)
 
+    def rigid_align(self, ref_img, this_band=0, ref_band=2, geobin_radius=32, num_geobins=128, margin=32):
+        si = otb.Registry.CreateApplication("Superimpose")
+        si.SetParameterInputImage('inm', self.pipe[-1].GetParameterOutputImage(self.out_p[-1]))
+        si.SetParameterString('inr', ref_img)
+        si.Execute()
+        sz = si.GetImageSize('out')
+        sz = min(sz[0], sz[1])
+        geobin_size = 2 * geobin_radius + 1
+        geobin_spacing = floor((sz - geobin_size * ceil(sqrt(num_geobins)) - 2 * margin) / 4)
+        sh = compute_displacement(si, to_otb_pipeline(ref_img), src_band=this_band, tgt_band=ref_band,
+                                  out_param_src='out', out_param_tgt='out',
+                                  geobin_size=geobin_size, geobin_spacing=geobin_spacing, margin=margin)
+        print(sh[0])
+        if sh is not None:
+            ref_spc = si.GetImageSpacing('out')
+            self.shift = (ref_spc[0]*sh[0][1], ref_spc[1]*sh[0][0])
+        return
+
     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:
+            spc = self.pipe[t].GetImageSpacing(self.out_p[t])
             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)
+            extent = list(ly.GetExtent())
+            extent = [extent[0] - buffer, extent[1] + buffer, extent[2] - buffer, extent[3] + buffer]
+            if spc[0] < 0:
+                tmp = extent[0]
+                extent[0] = extent[1]
+                extent[1] = tmp
+            if spc[1] < 0:
+                tmp = extent[2]
+                extent[2] = extent[3]
+                extent[3] = tmp
             if ly.GetSpatialRef().GetAuthorityCode('PROJCS') == '4326':
                 er.SetParameterString('mode.extent.unit', 'lonlat')
             else:
@@ -161,14 +192,15 @@ class SPOT67RasterPipeline:
 
     def write_outputs(self, fld, roi=None, update_pipe=False, compress=False):
         out = []
-        out_idx_bck = self.out_idx.copy()
+        out_idx_bck = proc_idx = self.out_idx.copy()
         if roi is not None:
             pipe_length = len(self.pipe)
             self.clip(roi)
+            proc_idx = self.out_idx.copy()
         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:
+        for t in proc_idx:
             out_file = os.path.join(fld, self.files[t])
             if compress:
                 out_file += '?gdal:co:compress=deflate&gdal:co:bigtiff=yes'
@@ -177,13 +209,23 @@ class SPOT67RasterPipeline:
             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()
+            if self.shift is not None:
+                print("INFO: Applying shift {} to current outputs.".format(self.shift))
+                ds = gdal.Open(out_file, 1)
+                geot = list(ds.GetGeoTransform())
+                geot[0] += self.shift[0]
+                geot[3] += self.shift[1]
+                ds.SetGeoTransform(tuple(geot))
+                ds = None
             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 update_pipe:
+            self.shift = None
         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
+        return out
diff --git a/moringa.py b/moringa.py
index 1eaefba171efc57561e1d3fa5c07fbe6cdc39fee..5dc753367900433f1e5d03b08b3d9684e2e01caa 100755
--- a/moringa.py
+++ b/moringa.py
@@ -12,16 +12,19 @@ def run_segmentation(img, threshold, cw, sw , out_seg,
     OBIA.segmentation.lsgrm(img, params, out_seg, n_proc, memory, None, remove_graph, force_parallel)
     return
 
-def preprocess_spot67(in_fld, out_fld, dem_fld, geoid_file, skip_ps, compress):
+def preprocess_spot67(in_fld, out_fld, dem_fld, geoid_file, skip_ps, compress,
+                      clip, align_to, align_to_band, align_using_band):
     sp = VHR.vhrbase.SPOT67RasterPipeline(in_fld)
     sp.to_toa()
     sp.orthorectify(dem_fld, geoid_file)
+    if clip is not None and os.path.exists(clip):
+        sp.clip(clip)
     if not skip_ps:
         sp.write_outputs(out_fld, update_pipe=True, compress=compress)
         sp.pansharp()
-        sp.write_outputs(out_fld, compress=compress)
-    else:
-        sp.write_outputs(out_fld, compress=compress)
+    if align_to is not None and os.path.exists(align_to):
+        sp.rigid_align(align_to, this_band=align_using_band-1, ref_band=align_to_band-1)
+    sp.write_outputs(out_fld, compress=compress)
     return
 
 def preprocess_s2(in_fld, out_fld, output_dates_file=None, roi=None, coregister_to=None, coregister_to_band=1,
@@ -56,7 +59,7 @@ def main(args):
     prepr.add_argument("--roi", type=str, default=None, help="Path to the ROI vector file.")
     prepr.add_argument("--coregister_to", type=str, default=None, help="Path to a reference image to which the stacks must be coregistered.")
     prepr.add_argument("--coregister_to_band", type=int, nargs='?', default=1, help="Band of reference image used for co-registration.")
-    prepr.add_argument("--coregister_using_band", type=int, nargs='?', default=3, help="Band of reference image used for co-registration.")
+    prepr.add_argument("--coregister_using_band", type=int, nargs='?', default=3, help="Band of current stack used for co-registration.")
     prepr.add_argument("--provider", type=str, default='theia', help="S2 image provider. Supported: 'theia', 'theial3a', 'sen2cor', 'planetary'")
 
     segmt = subpar.add_parser("segment", help="Performs (large scale Baatz-Shape) segmentation of an input image.",
@@ -79,6 +82,10 @@ def main(args):
     vhrprep.add_argument("out_fld", type=str, help="Path to the output folder for preprocessed images.")
     vhrprep.add_argument("dem_fld", type=str, help="Path to the folder containing DEM covering the scene in WGS84 projection.")
     vhrprep.add_argument("geoid", type=str, help="Path to the geoid file.")
+    vhrprep.add_argument("--clip", type=str, default=None, help="Path to a vector file for clipping.")
+    vhrprep.add_argument("--align_to", type=str, default=None, help="Path to a reference image to which the image must be aligned (rigid).")
+    vhrprep.add_argument("--align_to_band", type=int, nargs='?', default=3, help="Band of reference image used for alignment.")
+    vhrprep.add_argument("--align_using_band", type=int, nargs='?', default=1, help="Band of current image used for alignment.")
     vhrprep.add_argument("--skip_ps", help="Skip pansharpening step.", action='store_true')
     vhrprep.add_argument("--compress", help="Use lossless compresion on outputs.", action='store_true')
 
@@ -99,7 +106,8 @@ def main(args):
                          arg.n_proc, arg.mem_limit, not arg.keep_graph, arg.force_parallel)
 
     if arg.cmd == "preprocess_spot67":
-        preprocess_spot67(arg.fld, arg.out_fld, arg.dem_fld, arg.geoid, arg.skip_ps, arg.compress)
+        preprocess_spot67(arg.fld, arg.out_fld, arg.dem_fld, arg.geoid, arg.skip_ps, arg.compress,
+                          arg.clip, arg.align_to, arg.align_to_band, arg.align_using_band)
 
     return 0