Threshold:

Region Growing

Grow a region from a seed pixel

Click a region on the map. The computed region will be red.

This example uses a ol.source.Raster to generate data based on another source. The raster source accepts any number of input sources (tile or image based) and runs a pipeline of operations on the input data. The return from the final operation is used as the data for the output source.

In this case, a single tiled source of imagery data is used as input. The region is calculated in a single "image" operation using the "seed" pixel provided by the user clicking on the map. The "threshold" value determines whether a given contiguous pixel belongs to the "region" - the difference between a candidate pixel's RGB values and the seed values must be below the threshold.

This example also shows how an additional function can be made available to the operation.

raster, region growing
<!DOCTYPE html>
<html>
<head>
<title>Region Growing</title>
<script src="https://code.jquery.com/jquery-1.11.2.min.js"></script>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>
<link rel="stylesheet" href="http://openlayers.org/en/v3.9.0/css/ol.css" type="text/css">
<script src="http://openlayers.org/en/v3.9.0/build/ol.js"></script>

<style>
table.controls td {
  min-width: 110px;
  padding: 2px 5px;
}

</style>
</head>
<body>
<div class="container-fluid">

<div class="row-fluid">
  <div class="span12">
    <div id="map" class="map" style="cursor: pointer"></div>
    <table class="controls">
      <tr>
        <td>Threshold: <span id="threshold-value"></span></td>
        <td><input id="threshold" type="range" min="1" max="50" value="20"></td>
      </tr>
    </table>
  </div>
</div>

</div>
<script>
// NOCOMPILE
function growRegion(inputs, data) {
  var image = inputs[0];
  var seed = data.pixel;
  var delta = parseInt(data.delta);
  if (!seed) {
    return image;
  }

  seed = seed.map(Math.round);
  var width = image.width;
  var height = image.height;
  var inputData = image.data;
  var outputData = new Uint8ClampedArray(inputData);
  var seedIdx = (seed[1] * width + seed[0]) * 4;
  var seedR = inputData[seedIdx];
  var seedG = inputData[seedIdx + 1];
  var seedB = inputData[seedIdx + 2];
  var edge = [seed];
  while (edge.length) {
    var newedge = [];
    for (var i = 0, ii = edge.length; i < ii; i++) {
      // As noted in the Raster source constructor, this function is provided
      // using the `lib` option. Other functions will NOT be visible unless
      // provided using the `lib` option.
      var next = nextEdges(edge[i]);
      for (var j = 0, jj = next.length; j < jj; j++) {
        var s = next[j][0], t = next[j][1];
        if (s >= 0 && s < width && t >= 0 && t < height) {
          var ci = (t * width + s) * 4;
          var cr = inputData[ci];
          var cg = inputData[ci + 1];
          var cb = inputData[ci + 2];
          var ca = inputData[ci + 3];
          // if alpha is zero, carry on
          if (ca === 0) {
            continue;
          }
          if (Math.abs(seedR - cr) < delta && Math.abs(seedG - cg) < delta &&
              Math.abs(seedB - cb) < delta) {
            outputData[ci] = 255;
            outputData[ci + 1] = 0;
            outputData[ci + 2] = 0;
            outputData[ci + 3] = 255;
            newedge.push([s, t]);
          }
          // mark as visited
          inputData[ci + 3] = 0;
        }
      }
    }
    edge = newedge;
  }
  return new ImageData(outputData, width, height);
}

function next4Edges(edge) {
  var x = edge[0], y = edge[1];
  return [
    [x + 1, y],
    [x - 1, y],
    [x, y + 1],
    [x, y - 1]
  ];
}

var key = 'Ak-dzM4wZjSqTlzveKz5u0d4IQ4bRzVI309GxmkgSVr1ewS6iPSrOvOKhA-CJlm3';

var imagery = new ol.layer.Tile({
  source: new ol.source.BingMaps({key: key, imagerySet: 'Aerial'})
});

var raster = new ol.source.Raster({
  sources: [imagery.getSource()],
  operationType: 'image',
  operation: growRegion,
  // Functions in the `lib` object will be available to the operation run in
  // the web worker.
  lib: {
    nextEdges: next4Edges
  }
});

var rasterImage = new ol.layer.Image({
  opacity: 0.7,
  source: raster
});

var map = new ol.Map({
  layers: [imagery, rasterImage],
  target: 'map',
  view: new ol.View({
    center: ol.proj.fromLonLat([-119.07, 47.65]),
    zoom: 11
  })
});

var coordinate;

map.on('click', function(event) {
  coordinate = event.coordinate;
  raster.changed();
});

raster.on('beforeoperations', function(event) {
  // the event.data object will be passed to operations
  var data = event.data;
  data.delta = thresholdControl.value;
  if (coordinate) {
    data.pixel = map.getPixelFromCoordinate(coordinate);
  }
});

var thresholdControl = document.getElementById('threshold');

function updateControlValue() {
  document.getElementById('threshold-value').innerText = thresholdControl.value;
}
updateControlValue();

thresholdControl.addEventListener('input', function() {
  updateControlValue();
  raster.changed();
});

</script>
</body>
</html>