diff --git a/OBIA/OBIABase.py b/OBIA/OBIABase.py
new file mode 100644
index 0000000000000000000000000000000000000000..eead7fb425d7155778d0f9933a05e32f438574d7
--- /dev/null
+++ b/OBIA/OBIABase.py
@@ -0,0 +1,122 @@
+import os.path
+import sys
+import otbApplication as otb
+import gdal
+import numpy as np
+import pandas as pd
+from enum import Enum
+from Common.otb_numpy_proc import to_otb_pipeline
+from skimage.measure import regionprops
+
+class OStats(Enum):
+    MEAN = 'mean'
+    STD = 'std'
+    MIN = 'min'
+    MAX = 'max'
+
+def intensity_std(region, intensities):
+    # note the ddof arg to get the sample var if you so desire!
+    return np.std(intensities[region], ddof=1)
+
+class OBIABase:
+
+    def __init__(self, object_layer, nominal_tile_size=5000, background_label=0):
+        self.object_layer = object_layer
+        self.background_label = background_label
+        if type(nominal_tile_size) is tuple:
+            self.nominal_tile_size = nominal_tile_size
+        elif type(nominal_tile_size) is int:
+            self.nominal_tile_size = (nominal_tile_size, nominal_tile_size)
+        else:
+            sys.exit('Invalid nominal tile size')
+        self.tiles, self.obj_to_tile = self.generate_tile_info(self.nominal_tile_size, self.background_label)
+        # Init database
+        self.reset_db()
+
+    def reset_db(self):
+        self.database = pd.DataFrame(index=self.obj_to_tile.keys())
+
+    def generate_tile_info(self, nominal_tile_size, background_label):
+        in_seg = to_otb_pipeline(self.object_layer)
+        full = in_seg.GetImageAsNumpyArray('out')
+        rp = regionprops(full.astype(np.uint32))
+
+        W, H = in_seg.GetImageSize('out')
+        tx, ty = int(W / nominal_tile_size[0]) + 1, int(H / nominal_tile_size[1]) + 1
+
+        obj_to_tile = {}
+        tiles = dict.fromkeys(range(tx * ty))
+        for i in range(tx * ty):
+            tiles[i] = [np.inf, np.inf, 0, 0]
+
+        for o in rp:
+            if o.label != background_label:
+                ix, iy = int(o.bbox[1] / nominal_tile_size[0]), int(o.bbox[0] / nominal_tile_size[1])
+                idx = ix * ty + iy
+                obj_to_tile[o.label] = idx
+                tiles[idx][0] = min(o.bbox[1], tiles[idx][0])
+                tiles[idx][1] = min(o.bbox[0], tiles[idx][1])
+                tiles[idx][2] = max(o.bbox[3], tiles[idx][2])
+                tiles[idx][3] = max(o.bbox[2], tiles[idx][3])
+
+        tiles = {k: [v[0],v[1],v[2]-v[0],v[3]-v[1]] for k,v in tiles.items() if not np.isinf(v[0])}
+
+        return tiles, obj_to_tile
+
+    def compute_stats_on_tile(self, tile_num, input_image, stats=[OStats.MEAN]):
+        r = otb.itkRegion()
+        r['index'][0], r['index'][1] = self.tiles[tile_num][0], self.tiles[tile_num][1]
+        r['size'][0], r['size'][1] = self.tiles[tile_num][2], self.tiles[tile_num][3]
+
+        obj = to_otb_pipeline(self.object_layer)
+        obj.PropagateRequestedRegion('out', r)
+        clip_obj = obj.ExportImage('out')
+        tile_obj = np.squeeze(clip_obj['array']).astype(np.uint32)
+
+        si = otb.Registry.CreateApplication('Superimpose')
+        si.SetParameterString('inm', input_image)
+        si.SetParameterString('inr', self.object_layer)
+        si.SetParameterString('interpolator', 'nn')
+        si.Execute()
+        si.PropagateRequestedRegion('out', r)
+        clip_img = si.ExportImage('out')
+        tile_img = np.squeeze(clip_img['array'])
+
+        if OStats.STD in stats:
+            op = regionprops(tile_obj, intensity_image=tile_img, extra_properties=[intensity_std])
+        else:
+            op = regionprops(tile_obj, intensity_image=tile_img)
+
+        out_stats = {}
+        for o in op:
+            if self.obj_to_tile[o.label] == tile_num:
+                out_stats[o.label] = []
+                if OStats.MEAN in stats:
+                    out_stats[o.label].append(o.intensity_mean)
+                if OStats.STD in stats:
+                    out_stats[o.label].append(o.intensity_std)
+                if OStats.MIN in stats:
+                    out_stats[o.label].append(o.intensity_min)
+                if OStats.MAX in stats:
+                    out_stats[o.label].append(o.intensity_max)
+                out_stats[o.label] = np.concatenate(out_stats[o.label])
+
+        return out_stats.keys(), np.vstack(list(out_stats.values()))
+
+    def add_raster_stats(self, raster_file, raster_name=None, stats=[OStats.MEAN]):
+        if raster_name is None:
+            raster_name = os.path.splitext(os.path.basename(raster_file))[0]
+
+        ds = gdal.Open(raster_file)
+        n_bands = ds.RasterCount
+        ds = None
+
+        var_names = [raster_name + '_{}_{}'.format(i+1,j.value) for i in range(n_bands) for j in stats]
+        for t in self.tiles.keys():
+            k,v = self.compute_stats_on_tile(t, raster_file, stats)
+            self.database.loc[k,var_names] = v
+
+        return
+
+
+
diff --git a/VHR/segmentation.py b/OBIA/segmentation.py
similarity index 98%
rename from VHR/segmentation.py
rename to OBIA/segmentation.py
index af815013435b52ce359a1f686fa96b718037566a..5be2e7e19f89e60f0e25d633dc0a829b83d14e92 100644
--- a/VHR/segmentation.py
+++ b/OBIA/segmentation.py
@@ -20,7 +20,7 @@ class LSGRMParams:
     color_weight: float
     spatial_weight: float
     n_first_iter: int = 8
-    margin : int = 100
+    margin: int = 100
 
 def lsgrm_process_tile(input_image, params : LSGRMParams, tile_width, tile_height, tile_idx, out_graph, roi=None):
     if roi is None:
@@ -104,6 +104,7 @@ def lsgrm(input_image, params : LSGRMParams, out_seg, n_proc=None, memory=None,
         grm.SetParameterFloat('cw', params.color_weight)
         grm.SetParameterFloat('sw', params.spatial_weight)
         grm.SetParameterString('out', out_seg)
+        grm.SetParameterOutputImagePixelType('out', otb.ImagePixelType_uint32)
         grm.ExecuteAndWriteOutput()
 
     else:
@@ -134,6 +135,7 @@ def lsgrm(input_image, params : LSGRMParams, out_seg, n_proc=None, memory=None,
             write_qgis_seg_style(out_seg.replace(ext, '.qml'))
         else:
             agg_app.SetParameterString('out', out_seg)
+            agg_app.SetParameterOutputImagePixelType('out', otb.ImagePixelType_uint32)
             agg_app.ExecuteAndWriteOutput()
 
         if remove_graph:
@@ -265,7 +267,6 @@ def get_bounding_boxes(input_segm):
                 bboxes[lbl[i]][2] = max(bboxes[lbl[i]][2], rpos[i])
                 bboxes[lbl[i]][3] = y
 
-
     return bboxes
 
 
diff --git a/moringa.py b/moringa.py
index 25fecbe8340c27764476c3168d1fbd999096b9c9..ca764aade1256040f2eb25efa0e833b4456c9598 100755
--- a/moringa.py
+++ b/moringa.py
@@ -1,13 +1,13 @@
 import sys
 import argparse
-import VHR.segmentation
+import OBIA.segmentation
 import VHR.vhrbase
 
 def run_segmentation(img, threshold, cw, sw , out_seg,
                      n_first_iter, margin, n_proc, memory,
                      remove_graph, force_parallel):
-    params = VHR.segmentation.LSGRMParams(threshold, cw, sw, n_first_iter, margin)
-    VHR.segmentation.lsgrm(img,params,out_seg,n_proc,memory,None,remove_graph,force_parallel)
+    params = OBIA.segmentation.LSGRMParams(threshold, cw, sw, n_first_iter, margin)
+    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):