Region Growing

This example uses OpenLayers v4.0.1. The latest is v.

#### Region Growing

Threshold: |

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.

Related API documentation:

- ol.Map
,
- ol.View
,
- ol.layer.Image
,
- ol.layer.Tile
,
- ol.proj
,
- ol.source.BingMaps
,
- ol.source.Raster

Copy Edit
Region Growing Grow a region from a seed pixel 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 = next4Edges(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 {data: outputData, width: width, height: 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 = 'Your Bing Maps Key from http://www.bingmapsportal.com/ here'; 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: { next4Edges: 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(); }); var thresholdControl = document.getElementById('threshold'); 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); } }); function updateControlValue() { document.getElementById('threshold-value').innerText = thresholdControl.value; } updateControlValue(); thresholdControl.addEventListener('input', function() { updateControlValue(); raster.changed(); }); table.controls td { min-width: 110px; padding: 2px 5px; } <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>

    <!DOCTYPE html>
    <html>
      <head>
        <title>Region Growing</title>
        <link rel="stylesheet" href="https://openlayers.org/en/v4.0.1/css/ol.css" type="text/css">
        <!-- The line below is only needed for old environments like Internet Explorer and Android 4.x -->
        <script src="https://cdn.polyfill.io/v2/polyfill.min.js?features=requestAnimationFrame,Element.prototype.classList,URL"></script>
        <script src="https://openlayers.org/en/v4.0.1/build/ol.js"></script>
        <style>
          table.controls td {
            min-width: 110px;
            padding: 2px 5px;
          }
        </style>
      </head>
      <body>
        <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>
        <script>
          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 = next4Edges(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 {data: outputData, width: width, height: 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 = 'Your Bing Maps Key from http://www.bingmapsportal.com/ here';

          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: {
              next4Edges: 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();
          });

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

          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);
            }
          });

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

          thresholdControl.addEventListener('input', function() {
            updateControlValue();
            raster.changed();
          });
        </script>
      </body>
    </html>