1

Image Stripes after applying a Gamma correction after normalization with this Sentinel2, 4 Band DN using Leaflet (Complete Code) with this code:

  <body>
<div id="map"></div>
<script src="https://unpkg.com/[email protected]/dist/leaflet.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/proj4js/2.6.0/proj4.js"></script>
<script src="https://unpkg.com/georaster"></script>
<script src="https://unpkg.com/georaster-layer-for-leaflet"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/proj4leaflet/1.0.2/proj4leaflet.min.js"></script>
<script src="https://unpkg.com/leaflet-proj4leaflet"></script> <!-- Import Proj4Leaflet extension -->

<script>
  // Define custom CRS for EPSG 2056
  var crs2056 = new L.Proj.CRS('EPSG:2056',"+proj=somerc +lat_0=46.95240555555556 +lon_0=7.439583333333333 +k_0=1 +x_0=2600000 +y_0=1200000 +ellps=bessel +towgs84=674.374,15.056,405.346,0,0,0,0 +units=m +no_defs",  {    resolutions: [1024, 512, 256, 128, 64, 32, 16, 8, 4, 2, 1, 0.5, 0.25, 0.125, 0.0625],
origin: [2485071.58, 1299941.79],  } );

  // Initialize Leaflet map with custom CRS
  var map = L.map('map', {
    crs: crs2056
  }).setView([46, 8], 1);
  
var map4 = new L.tileLayer.wms("https://wms0.geo.admin.ch/", {
layers: 'ch.vbs.milairspacechart',
format: 'image/png',
transparent: true,
crs: crs2056
}).addTo(map);

  // Add OpenStreetMap basemap
  //L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', {
  //    attribution: '&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
  //}).addTo(map);

  const url_to_geotiff_file = new URLSearchParams(location.search).get("url");
  console.log("url_to_geotiff_file:", url_to_geotiff_file);

  if (!url_to_geotiff_file) {
    setTimeout(function() {
      // If no URL is provided, redirect to a sample GeoTIFF
      const parser = new URL(window.location);
      parser.searchParams.set("url", "https://d29cp2gnktw6by.cloudfront.net/data/S2_LEVEL_2A/20240101T102431/S2_L2A_SR_20240101T102431_20240101T102428_10M_run20240102.tif");
      window.location = parser.href;
    }, 2 * 1000);
  } else {
    console.log("url_to_geotiff_file:", url_to_geotiff_file);
    // Parse the GeoTIFF data
    parseGeoraster(url_to_geotiff_file).then(function (georaster) {
      console.log("georaster:", georaster);

      const vizParamsWATER = {
        bands: ['B4', 'B3', 'B2'],
        min: 400,
        max: 3200,
        gamma: [1.7, 1.7, 1.4]
      };

      const layer = new GeoRasterLayer({
        attribution: "'Contains modified Copernicus Sentinel data [Year]' for Sentinel data",
        debugLevel: 0,
        georaster,
        resolution: 512,
        resampleMethod: "nearest",
        pixelValuesToColorFn: values => {
          // Check if any of the bands have a value of 9999 or 0
          if (values.some(value => value === 9999 || value === 0)) {
            // If any band has a value of 9999 or 0, return transparent color
            return 'rgba(0, 0, 0, 0)'; // Transparent color
          } else {
            // Apply min-max normalization to each band
            const r = (values[0] - vizParamsWATER.min) / (vizParamsWATER.max - vizParamsWATER.min);
            const g = (values[1] - vizParamsWATER.min) / (vizParamsWATER.max - vizParamsWATER.min);
            const b = (values[2] - vizParamsWATER.min) / (vizParamsWATER.max - vizParamsWATER.min);

            // Apply gamma correction to each band
            const gammaR = Math.pow(r, 1 / vizParamsWATER.gamma[0]);
            const gammaG = Math.pow(g, 1 / vizParamsWATER.gamma[1]);
            const gammaB = Math.pow(b, 1 / vizParamsWATER.gamma[2]);

            // Return RGB color
            return `rgba(${gammaR * 255},${gammaG * 255},${gammaB * 255})`;
          }
        }
      });

      layer.addTo(map);
      map.fitBounds(layer.getBounds());
    });
  }
</script>

I get the following result

A) Artefacts, Lines in the image, (I do get the same artefacts if I use EPSG 3857 code)

Artefacts

If I apply only min max normalization like this

                // Apply min-max normalization to each band
            const r = Math.round((values[0] - vizParamsWATER.min) / (vizParamsWATER.max - vizParamsWATER.min)*255) ;
            const g = Math.round((values[1] - vizParamsWATER.min) / (vizParamsWATER.max - vizParamsWATER.min)*255) ;
            const b = Math.round((values[2] - vizParamsWATER.min) / (vizParamsWATER.max - vizParamsWATER.min)*255) ;
            return `rgba(${r},${g},${b})`;

then there are no stripes. Seems like the Gamma Correction is the issue:

// Apply gamma correction to each band
            const gammaR = Math.pow(r, 1 / vizParamsWATER.gamma[0]);
            const gammaG = Math.pow(g, 1 / vizParamsWATER.gamma[1]);
            const gammaB = Math.pow(b, 1 / vizParamsWATER.gamma[2]);

expected result: -> Applied gamma correction with no stripes in the imagery

1
  • Problem is on your [r, g, b] conversion formulas. Sometimes they return negative value, so computed gamma value has NaN value then. Just checking for negative value and setting it to 0 works.
    – TomazicM
    Commented Feb 18 at 16:02

1 Answer 1

1

Solved. I used this approach (formulas come from here https://custom-scripts.sentinel-hub.com/custom-scripts/sentinel-2/l2a_optimized/):

parseGeoraster(url_to_geotiff_file).then(function (georaster) {
        console.log("georaster:", georaster);
        // based on https://custom-scripts.sentinel-hub.com/custom-scripts/sentinel-2/l2a_optimized/    
        const maxR = 3.0; // max reflectance
        const midR = 0.13;
        const sat = 1.2;
        const gamma = 1.8;
        const gOff = 0.01;
        const gOffPow = Math.pow(gOff, gamma);
        const gOffRange = Math.pow(1 + gOff, gamma) - gOffPow;

        function sAdj(a) {
            return adjGamma(adj(a, midR, 1, maxR));
        }

        function adjGamma(b) {
            return (Math.pow((b + gOff), gamma) - gOffPow) / gOffRange;
        }

        function satEnh(r, g, b) {
            const avgS = (r + g + b) / 3.0 * (1 - sat);
            return [clip(avgS + r * sat), clip(avgS + g * sat), clip(avgS + b * sat)];
        }

        function clip(s) {
            return s < 0 ? 0 : s > 1 ? 1 : s;
        }

        function adj(a, tx, ty, maxC) {
            var ar = clip(a / maxC, 0, 1);
            return ar * (ar * (tx / maxC + ty - 1) - ty) / (ar * (2 * tx / maxC - 1) - tx / maxC);
        }

        //const sRGB = (c) => c <= 0.0031308 ? (12.92 * c) : (1.055 * Math.pow(c, 0.41666666666) - 0.055);
        const layer = new GeoRasterLayer({
            attribution: "Contains modified Copernicus Sentinel data 2024",
            debugLevel: 0,
            georaster,
            resolution: 512,
            resampleMethod: "nearest",
            pixelValuesToColorFn: values => {
                // Check if any of the bands have a value of 9999 or 0
                if (values.some(value => value === 9999 || value === 0)) {
                    // If any band has a value of 9999 or 0, return transparent color
                    return 'rgba(0, 0, 0, 0)'; // Transparent color
                } else {
                    // Apply min-max normalization to each band
                    const r = (Math.round((sAdj(values[0]/10000))*255));
                    const g = (Math.round((sAdj(values[1]/10000))*255));
                    const b = (Math.round((sAdj(values[2]/10000))*255));

                    // Return RGB color
                    return `rgba(${r},${g},${b},1)`;
                }
            }
        });

        layer.addTo(map);
        map.fitBounds(layer.getBounds());
    });

This is how it looks then:

enter image description here

1

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.