├── WaterQuality_LakeErie ├── BloomDetectionAlgs.png ├── SelectBloomDetectionAlgs.png ├── convertToLplusDOsubtract.js ├── LakeErie_unmix_test.js ├── calcACCA.js ├── Simplified Script for Landsat Water Quality.js ├── Ho et al. (2017) Bloom Detection Algs.js └── Ho et al. (2017) Select Bloom Detection Algs.js └── README.md /WaterQuality_LakeErie/BloomDetectionAlgs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffcfho/GEE_CodeEditorScripts/HEAD/WaterQuality_LakeErie/BloomDetectionAlgs.png -------------------------------------------------------------------------------- /WaterQuality_LakeErie/SelectBloomDetectionAlgs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffcfho/GEE_CodeEditorScripts/HEAD/WaterQuality_LakeErie/SelectBloomDetectionAlgs.png -------------------------------------------------------------------------------- /WaterQuality_LakeErie/convertToLplusDOsubtract.js: -------------------------------------------------------------------------------- 1 | //Conversion of Raw DN to spectral radiance + DO subtraction 2 | var convertToLplusDOsubtract = function(image) { 3 | var Llambda = ee.Algorithms.Landsat.calibratedRadiance(image); 4 | var geom = image.geometry() 5 | var DO = Llambda.reduceRegion({ 6 | reducer: ee.Reducer.min(), 7 | scale:30, 8 | geometry: geom, 9 | bestEffort: true}); 10 | return Llambda.subtract(DO.toImage()) 11 | .copyProperties(image,['system:time_end']); 12 | }; 13 | 14 | 15 | var col = ee.ImageCollection('LANDSAT/LT5_L1T').filterBounds(ee.Feature.Point(-83,41.76)) 16 | .filterDate(new Date('7/1/2002'),new Date('10/31/2002')); 17 | var LminusDO = col.map(convertToLplusDOsubtract); 18 | print(col.first()); 19 | print(LminusDO.first()); 20 | 21 | Map.addLayer(ee.Image(col.first()),{bands:['B3', 'B2', 'B1']}, 'Raw DN'); 22 | Map.addLayer(ee.Image(LminusDO.first()),{'min': 10, 'max': 60, bands:['B3', 'B2', 'B1']}, 23 | 'DO-subtracted spectral radiance ') 24 | Map.setCenter(-82.5,41.8,9); -------------------------------------------------------------------------------- /WaterQuality_LakeErie/LakeErie_unmix_test.js: -------------------------------------------------------------------------------- 1 | /* Testing the GEE Spectral Unmixing algorithm on an algal bloom image */ 2 | 3 | var training = ee.Image('LT5_L1T_TOA/LT50190312011246EDC00') 4 | Map.addLayer(training,{bands:['B3','B2','B1'], 'gamma': 2},'True Color') 5 | Map.centerObject(training) 6 | 7 | // Use the inspector to get some sample endmembers. 8 | var bloom = [0.11526162177324295, 9 | 0.10372377187013626, 10 | 0.06162901595234871, 11 | 0.029143797233700752, 12 | 0.002098856261000037, 13 | 295.09185791015625, 14 | -0.0009386111050844193]; 15 | var water = [0.10245747864246368, 16 | 0.0703064575791359, 17 | 0.044728368520736694, 18 | 0.025622442364692688, 19 | 0.004366065841168165, 20 | 293.7694396972656, 21 | -0.0009386111050844193]; 22 | var sediment = [0.1392693817615509, 23 | 0.1404828131198883, 24 | 0.11514773219823837, 25 | 0.057314638048410416, 26 | 0.004366065841168165, 27 | 295.529541015625, 28 | -0.0009386111050844193]; 29 | 30 | // Now use spectral unmixing to delineate these three types cleanly. 31 | var unmixed = training.unmix([sediment, bloom, water]); 32 | unmixed = unmixed.rename(['sediment', 'bloom', 'water']); 33 | 34 | Map.addLayer(unmixed, {}, 'unmixed'); 35 | 36 | 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Scripts cloned from Google Earth Engine code editor 2 | 3 | This repository holds my scripts using the GEE Javascript API. 4 | 5 | Currently, the only scripts here are those exploring [algal bloom detection over Lake Erie](WaterQuality_LakeErie). 6 | 7 | Two scripts in particular, [Bloom Detection Algs](https://github.com/jeffcfho/GEE_CodeEditorScripts/blob/master/WaterQuality_LakeErie/Ho%20et%20al.%20(2017)%20Bloom%20Detection%20Algs.js) and [Select Bloom Detection Algs](https://github.com/jeffcfho/GEE_CodeEditorScripts/blob/master/WaterQuality_LakeErie/Ho%20et%20al.%20(2017)%20Select%20Bloom%20Detection%20Algs.js), compare the different algal bloom detection algorithms we tested in [Ho et al. (2017), "Using Landsat to extend the historical record of lacustrine phytoplankton blooms: A Lake Erie case study"](https://www.sciencedirect.com/science/article/pii/S0034425716304928). 8 | 9 | The code in this repo is provided under an [Attribution 4.0 International (CC BY 4.0) license](https://creativecommons.org/licenses/by/4.0/). However, if you use the code we request that you contact us at jeffho [at] alumni.stanford.edu and michalak [at] stanford.edu to let us know that, and how, you are using the code. 10 | 11 | To view to their fullest potential, copy the scripts into the [Google Earth Engine code editor](https://code.earthengine.google.com/) and hit Run. The outputs should look something like this: 12 | 13 | ![BloomDetectionAlgs](WaterQuality_LakeErie/BloomDetectionAlgs.png) 14 | ![SelectBloomDetectionAlgs](WaterQuality_LakeErie/SelectBloomDetectionAlgs.png) 15 | -------------------------------------------------------------------------------- /WaterQuality_LakeErie/calcACCA.js: -------------------------------------------------------------------------------- 1 | var calcACCA = function(image) { 2 | var f1 = image.select('B3').lt(0.08); // B3 below 0.08 is non-cloud 3 | var f2 = image.normalizedDifference(['B2','B5']).gt(0.7); // (B2-B5)/(B2+B5) above 0.7 is non-cloud 4 | var f3 = image.select('B6').gt(300); // B6 above this is non-cloud 5 | 6 | var f4 = image.expression("( 1 - b('B5') ) * b('B6')").gt(225); // (1-B5)*B6 above this is ambiguous 7 | var f5 = image.expression("b('B4') / b('B3')").gt(2.0); // B4/B3 above this is ambiguous 8 | var f6 = image.expression("b('B4') / b('B2')").gt(2.0); // B4/B2 above this is ambiguous 9 | var f7 = image.expression("b('B4') / b('B5')").lt(1.0); // B4/B5 below this is ambiguous 10 | 11 | // Note: snow not expected in these summer scenes, so filter 8 not used 12 | //var f8 = image.expression("b('b5') / b('b6')").gt(210); // B5/B6 above this is warm cloud (below is cold cloud) 13 | 14 | // For speed of computation, Pass two cloud mask is not used, as would require histogramming each image 15 | // In this application, precise cloud estimates are not necessary 16 | // Therefore, to be conservative, ambiguous pixels are treated as clouds 17 | // Meaning that only the first three thresholds are used 18 | 19 | //ACCA Pass One 20 | var nonclouds = f1.or(f2).or(f3); 21 | var ambiguous = nonclouds.not().and(f4.or(f5).or(f6).or(f7)); 22 | var clouds = nonclouds.not().and(ambiguous.not()); 23 | 24 | //ACCA Pass Two 25 | // No check for desert or snow because not expected in these scenes 26 | var meanTemp = image.select('B6').mask(clouds).reduceRegion({ 27 | reducer: ee.Reducer.mean(), 28 | bestEffort: true}); 29 | var p975 = ee.Image(ee.Number( 30 | image.select('B6').mask(clouds).reduceRegion({ 31 | reducer: ee.Reducer.percentile([97.5]), 32 | bestEffort: true}).get('B6') 33 | )); 34 | var p835 = ee.Image(ee.Number( 35 | image.select('B6').mask(clouds).reduceRegion({ 36 | reducer: ee.Reducer.percentile([83.5]), 37 | bestEffort: true}).get('B6') 38 | )); 39 | 40 | var amb_temps = image.select('B6').mask(ambiguous); 41 | var toAreaKM = ee.Image(30*30/1000/1000); 42 | var L5_TM_footprint = ee.Image(170*183); //http://landsat.usgs.gov/band_designations_landsat_satellites.php 43 | 44 | var uppThermalEffect = amb_temps.lt(p975).and(amb_temps.gt(p835)); 45 | 46 | var upp_portion = ee.Image(ee.Number( 47 | uppThermalEffect.reduceRegion({ 48 | reducer: ee.Reducer.sum(), 49 | bestEffort: true}).get('B6') 50 | )).multiply(toAreaKM).divide(L5_TM_footprint); 51 | var upp_meantemp = ee.Image(ee.Number( 52 | amb_temps.reduceRegion({ 53 | reducer: ee.Reducer.mean(), 54 | bestEffort: true}).get('B6') 55 | )); 56 | 57 | var lowThermalEffect = amb_temps.lt(p835); 58 | var low_portion = ee.Image(ee.Number( 59 | lowThermalEffect.reduceRegion({ 60 | reducer: ee.Reducer.sum(), 61 | bestEffort: true}).get('B6') 62 | )).multiply(toAreaKM).divide(L5_TM_footprint); 63 | var low_meantemp = ee.Image(ee.Number( 64 | amb_temps.reduceRegion({ 65 | reducer: ee.Reducer.mean(), 66 | bestEffort: true}).get('B6') 67 | )); 68 | 69 | // Both thermal effect classes become cloud pixels if upper mean < 295K AND 70 | // upper thermal effect portion of scene < 0.4 71 | var ambClouds = (uppThermalEffect.or(lowThermalEffect)) 72 | .multiply(upp_meantemp.lt(295)).multiply(upp_portion.lt(0.4)); 73 | 74 | // Only lower thermal effect class becomes cloud pixels if low mean < 295K AND 75 | // lower thermal effect portion of scene < 0.4 76 | var ambClouds2 = lowThermalEffect 77 | .multiply(low_meantemp.lt(295)).multiply(low_portion.lt(0.4)); 78 | 79 | // Unite ambiguous cloud designations with previous clouds 80 | clouds = clouds.or(ambClouds.unmask()).or(ambClouds2.unmask()); 81 | 82 | return image.addBands(clouds.rename('cloud')); 83 | }; 84 | 85 | //Dates to try: 8/25/2008, 9/10/2008, 9/7/2010, 9/23/2010, 8/9/2011 86 | var TOA_col = ee.ImageCollection('LANDSAT/LT5_L1T_TOA').filterBounds(ee.Feature.Point(-83,41.76)) 87 | //.filterDate(new Date('9/22/2010'),new Date('9/24/2010')); //thin wispy 88 | .filterDate(new Date('8/24/2008'),new Date('8/26/2008')); //dotty 89 | //.filterDate(new Date('9/9/2008'),new Date('9/11/2008')); //disrupting scene 90 | var TOA = calcACCA(ee.Image(TOA_col.first())); 91 | 92 | Map.addLayer(TOA,{bands:['B3', 'B2', 'B1']}, 'TOA reflectance'); 93 | Map.addLayer(TOA.select('cloud'),{},'Cloud'); 94 | Map.setCenter(-82.5,41.8,9); -------------------------------------------------------------------------------- /WaterQuality_LakeErie/Simplified Script for Landsat Water Quality.js: -------------------------------------------------------------------------------- 1 | /**** Start of imports. If edited, may not auto-convert in the playground. ****/ 2 | var hdpoly = /* color: 0B4A8B */ee.Geometry.Polygon( 3 | [[[-83.57025146484375, 41.69451375429399], 4 | [-83.3258056640625, 41.66784681529111], 5 | [-83.089599609375, 41.591888125847646], 6 | [-82.9632568359375, 41.505556308050686], 7 | [-82.85064697265625, 41.51172668948318], 8 | [-82.54302978515625, 41.99732721531279], 9 | [-82.606201171875, 42.038137999741075], 10 | [-82.6995849609375, 42.04221763769271], 11 | [-82.82867431640625, 42.00549146736489], 12 | [-82.935791015625, 41.98507887314324], 13 | [-83.03466796875, 42.03609808254534], 14 | [-83.1060791015625, 42.05445497999412], 15 | [-83.1060791015625, 42.14208693993462], 16 | [-83.07861328125, 42.209257234286305], 17 | [-83.11019897460938, 42.26768689318153], 18 | [-83.15963745117188, 42.236175276963394], 19 | [-83.199462890625, 42.09570676103531], 20 | [-83.2708740234375, 41.9421911163313], 21 | [-83.33404541015625, 41.94014812213756], 22 | [-83.3697509765625, 41.8829177113481], 23 | [-83.47137451171875, 41.792880552910205], 24 | [-83.51875305175781, 41.70292500835719]]]); 25 | /***** End of imports. If edited, may not auto-convert in the playground. *****/ 26 | /// Simplified Script for Landsat Water Quality 27 | // Produces a map of an algal bloom in Lake Erie on 2011/9/3 28 | // Created on 12/7/2015 by Jeff Ho 29 | 30 | // VISUALIZATION PARAMETERS ------------------------------------------------------------------------- 31 | var startDate = ee.Date.fromYMD(2011,9,3); 32 | //var startDate = ee.Date.fromYMD(2011,8,18); // has some clouds 33 | //var startDate = ee.Date.fromYMD(2010,9,23); // very cloudy 34 | var num_days = 16; // the number of days in the date range 35 | 36 | var truecolor = 1; // show true color image as well 37 | var testThresh = false; // add a binary image classifying into "bloom"and "non-bloom 38 | var bloomThreshold = 0.02346; //threshold for classification fit based on other data 39 | var greenessThreshold = 1.6; 40 | 41 | /// FUNCTIONS -------------------------------------------------------------------------------------- 42 | 43 | // Implements the Automatic Cloud Cover Assessment, with some changes 44 | // http://landsathandbook.gsfc.nasa.gov/pdfs/ACCA_SPIE_paper.pdf 45 | var calcACCA = function(image) { 46 | var f1 = image.select('B3').lt(0.08); // B3 below 0.08 is non-cloud 47 | var f2 = image.normalizedDifference(['B2','B5']).gt(0.7); // (B2-B5)/(B2+B5) above 0.7 is non-cloud 48 | var f3 = image.select('B6').gt(300); // B6 above this is non-cloud 49 | var f4 = image.expression("( 1 - b('B5') ) * b('B6')").gt(225); // (1-B5)*B6 above this is ambiguous 50 | var f5 = image.expression("b('B4') / b('B3')").gt(2.0); // B4/B3 above this is ambiguous 51 | var f6 = image.expression("b('B4') / b('B2')").gt(2.0); // B4/B2 above this is ambiguous 52 | var f7 = image.expression("b('B4') / b('B5')").lt(1.0); // B4/B5 below this is ambiguous 53 | 54 | // Note: snow not expected in these summer scenes, so filter 8 not used 55 | //var f8 = image.expression("b('b5') / b('b6')").gt(210); // B5/B6 above this is warm cloud (below is cold cloud) 56 | 57 | //ACCA Pass One 58 | var nonclouds = f1.or(f2).or(f3); 59 | var ambiguous = nonclouds.not().and(f4.or(f5).or(f6).or(f7)); 60 | var clouds = nonclouds.not().and(ambiguous.not()); 61 | 62 | return image.addBands(clouds.rename('cloud')); 63 | }; 64 | 65 | var maskClouds = function(image) { 66 | // Get regions with clouds: 67 | var yesCloud = calcACCA(image).select("cloud").eq(0); 68 | // Remove clouds by making them 0: 69 | return image.updateMask(yesCloud); 70 | }; 71 | 72 | // Specifies a threshold for hue to estimate "green" pixels 73 | // this is used as an additional filter to refine the algorithm above 74 | var calcGreenness = function (img) { 75 | // map r, g, and b for more readable algebra below 76 | var r = img.select(['B3']); 77 | var g = img.select(['B2']); 78 | var b = img.select(['B1']); 79 | 80 | // calculate intensity, hue 81 | var I = r.add(g).add(b).rename(['I']); 82 | var mins = r.min(g).min(b).rename(['mins']); 83 | var H = mins.where(mins.eq(r), 84 | (b.subtract(r)).divide(I.subtract(r.multiply(3))).add(1) ); 85 | H = H.where(mins.eq(g), 86 | (r.subtract(g)).divide(I.subtract(g.multiply(3))).add(2) ); 87 | H = H.where(mins.eq(b), 88 | (g.subtract(b)).divide(I.subtract(b.multiply(3))) ); 89 | 90 | //pixels with hue below 1.6 more likely to be bloom and not suspended sediment 91 | var Hthresh = H.lte(1.6); 92 | 93 | return H.rename('H'); 94 | }; 95 | 96 | // Apply bloom detection algorithm 97 | var calcAlgorithm1 = function(image) { 98 | // Algorithm 1 based on: 99 | // Wang, M., & Shi, W. (2007). The NIR-SWIR combined atmospheric 100 | // correction approach for MODIS ocean color data processing. 101 | // Optics Express, 15(24), 15722–15733. 102 | 103 | // Add secondary filter using greenness function below 104 | image = image.addBands(calcGreenness(image)); 105 | 106 | // Apply algorithm 1: B4 - 1.03*B5 107 | var bloom1 = image.select('B4').subtract(image.select('B5').multiply(1.03)).rename('bloom1'); 108 | 109 | // Get binary image by applying the threshold 110 | var bloom1_mask = image.select("H").lte(greenessThreshold).rename(["bloom1_mask"]); 111 | 112 | //return original image + bloom, bloom_thresh 113 | return image.addBands(image) 114 | .addBands(bloom1) 115 | .addBands(bloom1_mask); 116 | }; 117 | 118 | // Apply bloom detection algorithm 119 | var calcAlgorithm2 = function(image) { 120 | // Algorithm 2 based on: 121 | // Matthews, M. (2011) A current review of empirical procedures 122 | // of remote sensing in inland and near-coastal transitional 123 | // waters, International Journal of Remote Sensing, 32:21, 124 | // 6855-6899, DOI: 10.1080/01431161.2010.512947 125 | 126 | // Apply algorithm 2: B2/B1 127 | var bloom2 = image.select('B2') 128 | .divide(image.select('B1')) 129 | .rename(['bloom2']); 130 | 131 | //return original image + bloom, bloom_thresh 132 | return image.addBands(image) 133 | .addBands(bloom2); 134 | }; 135 | 136 | 137 | /// APPLY ALGORITHM TO IMAGE ----------------------------------------------------------------------- 138 | 139 | // Filter to specific image 140 | var collection = ee.ImageCollection('LT5_L1T_TOA') 141 | .filterDate(startDate, startDate.advance(num_days, 'day')); 142 | 143 | // Apply algorithms 144 | collection = collection.map(calcAlgorithm1); 145 | collection = collection.map(calcAlgorithm2); 146 | 147 | // Mask out clouds 148 | collection = collection.map(maskClouds); 149 | 150 | 151 | /// VISUALIZATION -------------------------------------------------------------------- 152 | 153 | // Color palettes for bloom and bloom_thresh bands 154 | var bPal = ["7C7062", //land -2 155 | "FFFFFF", //clouds -1 156 | "0000ff", //non-bloom 0 157 | "00FF00" //bloom 1 158 | ]; 159 | var cPal = ['000000', // black 0.014 and below 160 | 'ff00ff', // purple 0.017 161 | '0000ff', // blue 0.020 162 | '00ffff', // cyan 0.023 163 | '00ff00', // green 0.026 164 | 'ffff00', // yellow 0.029 165 | 'ffa500', // orange 0.032 166 | 'ff0000', // red 0.035 167 | '660000', // red 0.038 168 | ]; 169 | 170 | // Create land mask using MODIS annual land cover 171 | var land_mask = ee.Image('MCD12Q1/MCD12Q1_005_2001_01_01').select('Land_Cover_Type_1').neq(0); 172 | var water_mask = ee.Image('MCD12Q1/MCD12Q1_005_2001_01_01').select('Land_Cover_Type_1').eq(0); 173 | 174 | // Add layers to map for visualization 175 | Map.addLayer(collection, {bands:['B3','B2','B1'], min:0, max:0.3, 'gamma': 1.2},'True Color'); 176 | Map.addLayer(land_mask.mask(land_mask), {min:0, max:1, palette: "000000"}, 'Land Mask'); 177 | Map.addLayer(water_mask.mask(water_mask), {min:0, max:1, palette: "AAAAAA"}, 'Water Mask', false); 178 | 179 | var bloom_image = collection.mosaic(); 180 | Map.addLayer( 181 | bloom_image.select("bloom1").updateMask(water_mask), 182 | {min:0.014,max:0.038,palette: cPal}, 183 | 'Algorithm 1: Raw Bloom', 184 | false); 185 | Map.addLayer( 186 | bloom_image.select("bloom1").updateMask(water_mask.and(bloom_image.select('bloom1_mask'))), 187 | {min:0.014,max:0.038,palette: cPal}, 188 | 'Algorithm 1: Bloom Plus Filter'); 189 | Map.addLayer( 190 | bloom_image.select("bloom2").updateMask(water_mask), 191 | {min:0.76,max:1,palette: cPal}, 192 | 'Algorithm 2: Raw Bloom', 193 | true); 194 | -------------------------------------------------------------------------------- /WaterQuality_LakeErie/Ho et al. (2017) Bloom Detection Algs.js: -------------------------------------------------------------------------------- 1 | // Comparing algal bloom detection algorithms (Ho et al., 2017) 2 | 3 | // Implements the Automatic Cloud Cover Assessment, with some changes 4 | // http://landsathandbook.gsfc.nasa.gov/pdfs/ACCA_SPIE_paper.pdf 5 | var calcConsACCA = function(img) { 6 | var f1 = img.select('B3').lt(0.08); // B3 below 0.08 is non-cloud 7 | var f2 = img.normalizedDifference(['B2','B5']).gt(0.7); // (B2-B5)/(B2+B5) above 0.7 is non-cloud 8 | var f3 = img.select('B6').gt(300); // B6 above this is non-cloud 9 | 10 | /* Do not implement Pass Two here for simplicity, hence these 11 | estimates are conservative (i.e., all ambiguous is cloud) */ 12 | 13 | var clouds = (f1.or(f2).or(f3)).not(); 14 | 15 | return img.addBands(clouds.rename('cloud')); 16 | }; 17 | 18 | //Specifies a threshold for hue to estimate green pixels 19 | var calcGreenness = function (img) { 20 | var r = img.select(['B3']); 21 | var g = img.select(['B2']); 22 | var b = img.select(['B1']); 23 | var I = r.add(g).add(b).rename(['I']); 24 | var mins = r.min(g).min(b).rename(['mins']); 25 | 26 | var H = mins.where(mins.eq(r), 27 | (b.subtract(r)).divide(I.subtract(r.multiply(3))).add(1) ); 28 | H = H.where(mins.eq(g), 29 | (r.subtract(g)).divide(I.subtract(g.multiply(3))).add(2) ); 30 | H = H.where(mins.eq(b), 31 | (g.subtract(b)).divide(I.subtract(b.multiply(3))) ); 32 | var Hthresh = H.lte(1.6); //threshold of 1.6 fit as described in Ho et al. (2017) 33 | 34 | return Hthresh; 35 | }; 36 | 37 | // Implements the TOA algorithms after correcting for clouds 38 | var calcAlgorithms = function(img) { 39 | var img2 = calcConsACCA(img); 40 | var yesCloud = img2.select("cloud"); 41 | 42 | //add algorithm outputs as bands 43 | var img3 = img2.expression("b('B3')/b('B1')").select(["B3"],["RedToBlue"]); 44 | img3 = img3.addBands(img2.expression("b('B3')/b('B4')").select(['B3'],['RedToNIR']) ); 45 | img3 = img3.addBands(img2.expression("b('B2')/b('B1')").select(['B2'],['GreenToBlue']) ); 46 | img3 = img3.addBands(img2.expression("( b('B1')-b('B3') )/b('B2')") 47 | .select(['B1'],['BlueMinusRedOverGreen']) ); 48 | img3 = img3.addBands(img2.expression("47.7-9.21*(2.9594+1.6203*b('B3raw'))/(2.1572+0.9198*b('B1raw'))+" + 49 | "29.7*(3.5046+0.8950*b('B4raw'))/(2.1572+0.9198*b('B1raw'))-" + 50 | "118*(3.5046+0.8950*b('B4raw'))/(2.9594+1.6203*b('B3raw'))-" + 51 | "6.81*(3.1591+1.0111*b('B5raw'))/(2.9594+1.6203*b('B3raw'))+" + 52 | "41.9*(2.8122+1.3984*b('B7raw'))/(2.9594+1.6203*b('B3raw'))-" + 53 | "14.7*(2.8122+1.3984*b('B7raw'))/(3.5046+0.8950*b('B4raw'))") 54 | .select(['constant'],['PhycocyaninDetection']) ); 55 | img3 = img3.addBands(img2.select('B4').select(['B4'],['NIR']) ); 56 | img3 = img3.addBands(img2.expression("b('B4')-b('B5')").select(['B4'],['NIRwithSAC']) ); 57 | 58 | var img_impnirwithsac = img2.expression("b('B4')-1.03*b('B5')").select(["B4"],["ImprovedNIRwithSAC"]); 59 | var gness=calcGreenness(img); 60 | img_impnirwithsac = img_impnirwithsac.where(gness.eq(0),0); 61 | img3 = img3.addBands(img_impnirwithsac); 62 | 63 | img3=img3.addBands(img2.expression("b('B4') - b('B3')").select(["B4"],["NIRminusRed"])); 64 | img3=img3.addBands(img2.expression("(b('B4')-b('B5'))/(b('B3')-b('B5'))") 65 | .select(["B4"],["NIRoverRedwithSAC"])); 66 | img3=img3.addBands(img2.expression("( b('B4')-" + 67 | "(b('B4')-b('B1'))+(b('B1')-b('B5'))*(850-490)/(1650-490)" + 68 | ") / " + 69 | "( b('B3')-" + 70 | "(b('B3')-b('B1'))+(b('B1')-b('B5'))*(660-490)/(1650-490)" + 71 | ")").select(["B4"],["NIRoverRedwithBAC"]) ); 72 | img3=img3.addBands(img2.expression("(b('B4')-b('B3'))+0.5*(b('B3')-b('B5'))") 73 | .select(["B4"],["CurvatureAroundRed"])); 74 | 75 | var thresholds = ee.Image([0.462, 0.841, 5.93, 0.0327, 0.0277, 0.0235, 0.495]); 76 | var img4=img3.select(['RedToBlue','GreenToBlue','PhycocyaninDetection', 77 | 'NIR','NIRwithSAC','ImprovedNIRwithSAC','NIRoverRedwithSAC']) 78 | img4=img4.gte(thresholds); 79 | 80 | // mask out clouds 81 | img4=img4.mask(yesCloud.not()); 82 | 83 | return (img.addBands(yesCloud).addBands(img4)); 84 | }; 85 | 86 | // Function takes the MERIS_DN band and transforms into CI 87 | var transformMERIS = function(img) { 88 | img = img.rename('MERIS_DN'); 89 | var justCI = img.select('MERIS_DN'); 90 | var land = justCI.eq(252); 91 | var clouds = justCI.eq(253); 92 | 93 | justCI = justCI.mask(justCI.neq(252)); //mask land 94 | 95 | /* convert to CI based on 96 | CI = 10.^((double(DN)-10-1)/(250/2.5)-4) 97 | (itself based on: 98 | DN =1+(250/2.5)*(4+LOG10(CI))+10 99 | see Stumpf et al., 2012 100 | ) 101 | */ 102 | justCI = ee.Image(10).pow( 103 | justCI.double().subtract(10).subtract(1) 104 | .divide(250/2.5) 105 | .subtract(4) 106 | ); 107 | 108 | //See Stumpf et al. 2012 for thresh of 0.001 ~= 10^5 cells/mL 109 | justCI=justCI.gte(0.001); 110 | justCI = justCI.where(clouds,-0.01/6); 111 | 112 | return (img.addBands(justCI.rename('MERIS_CI')) 113 | .addBands(land.rename('landmask'))); 114 | }; 115 | 116 | var L5_TOA = ee.ImageCollection('LANDSAT/LT5_L1T_TOA'); 117 | var L5_DN = ee.ImageCollection('LANDSAT/LT5_L1T') 118 | .select(["B1","B2","B3","B4","B5","B6","B7"], 119 | ["B1raw","B2raw","B3raw","B4raw","B5raw","B6raw","B7raw"]); 120 | var L5_TOA_DN = L5_TOA.combine(L5_DN,true); // Phycocyanin algorithm uses DN, so combine with TOA 121 | 122 | var erieWBcenter = ee.Geometry.Point(-83.0,41.8); 123 | var img = L5_TOA_DN 124 | .filterBounds(erieWBcenter) 125 | .filterDate(ee.Date.fromYMD(2011,9,3).getRange('day')) 126 | .first(); 127 | var img = ee.Image(img); //manual cast so that EE recognizes it as an image 128 | var algs = calcAlgorithms(img); 129 | // Envisat-1 MERIS data provided by European Space Agency 130 | // (http://stanford.edu/~jeffho/images/esa_logo.gif) 131 | var merisDN = ee.Image("users/jeffreyh/MERIS_CI/envisat201124609031543CL3GL1e80CI"); 132 | var merisCI = transformMERIS(merisDN); 133 | 134 | // Create a map for each visualization option. 135 | var algorithmToView = 'ImprovedNIRwithSAC' 136 | // one of ['RedToBlue','GreenToBlue','PhycocyaninDetection', 137 | // 'NIR','NIRwithSAC','ImprovedNIRwithSAC','NIRoverRedwithSAC'] 138 | 139 | var land = ee.Image('ESA/GLOBCOVER_L4_200901_200912_V2_3').select('landcover').neq(210); 140 | land = land.clip(img.geometry()); // clip global landcover map to image 141 | algs = algs.mask(land.not()); //mask water 142 | var land_visParams = {min:1, max:1, palette:['3A4A50']}; 143 | var bloom_visParams = {min:0,max:1, palette:['000000','31a354']}; 144 | var agree_visParams = {min: -1, max: 1, palette:['ff0000','ffffff','000000']}; 145 | var maps = []; 146 | 147 | // Map 1 - Landsat 5 for same time 148 | var map = ui.Map(); 149 | map.add(ui.Label('Input Landsat image')); 150 | map.addLayer(img,{gamma: 1.3, min: 0, max: 0.3, bands: ['B3', 'B2', 'B1']},'L5 TC'); 151 | map.setControlVisibility(false); 152 | maps.push(map); 153 | 154 | // Map 2 - MERIS 155 | var map = ui.Map(); 156 | map.add(ui.Label('MERIS CI reference image')); 157 | map.addLayer(merisCI.select('landmask'),land_visParams,'MERIS CI Land'); 158 | map.addLayer(merisCI.select('MERIS_CI'),bloom_visParams,'Meris CI lake'); 159 | map.setControlVisibility(false); 160 | maps.push(map); 161 | 162 | // Map 3 - Algorithm to compare 163 | var map = ui.Map(); 164 | map.add(ui.Label('Landsat 5 '+algorithmToView)); 165 | map.addLayer(land,land_visParams,'Land'); 166 | map.addLayer(algs.select(algorithmToView),bloom_visParams,'L5 Improved NIRwithSAC'); 167 | map.setControlVisibility(false); 168 | maps.push(map); 169 | 170 | // Map 4 - Agreement vs omission errors vs commission errors 171 | var merisCIclipped = merisCI.select('MERIS_CI').clip(img.geometry()); 172 | var agree = merisCIclipped.eq(algs.select(algorithmToView)); 173 | var commission = algs.select(algorithmToView).gt(merisCIclipped); 174 | agree = agree.where(commission,-1); 175 | var map = ui.Map(); 176 | map.add(ui.Label('Agreement vs. Omission errors vs. Commission errors')); 177 | map.addLayer(land,land_visParams,'Land'); 178 | map.addLayer(agree,agree_visParams,'CommissionOmissionAgreement'); 179 | //red for commission, white for omission, black for agreement 180 | map.setControlVisibility(false); 181 | maps.push(map); 182 | 183 | var linker = ui.Map.Linker(maps); 184 | 185 | // Enable zooming on the top-left map. 186 | maps[0].setControlVisibility({zoomControl: true}); 187 | 188 | // Show the scale (e.g. '500m') on the bottom-right map. 189 | maps[3].setControlVisibility({scaleControl: true}); 190 | 191 | // Create a title. 192 | var title = ui.Label('Comparing algal bloom detection algorithms (Ho et al., 2017)', { 193 | stretch: 'horizontal', 194 | textAlign: 'center', 195 | fontWeight: 'bold', 196 | fontSize: '24px' 197 | }); 198 | 199 | // Create a grid of maps. 200 | var mapGrid = ui.Panel([ 201 | ui.Panel([maps[0], maps[2]], null, {stretch: 'both'}), 202 | ui.Panel([maps[1], maps[3]], null, {stretch: 'both'}) 203 | ], 204 | ui.Panel.Layout.Flow('horizontal'), {stretch: 'both'} 205 | ); 206 | 207 | // Add the maps and title to the ui.root. 208 | ui.root.widgets().reset([title, mapGrid]); 209 | ui.root.setLayout(ui.Panel.Layout.Flow('vertical')); 210 | 211 | // Center the maps at Lake Erie. 212 | maps[0].centerObject(erieWBcenter,7); 213 | -------------------------------------------------------------------------------- /WaterQuality_LakeErie/Ho et al. (2017) Select Bloom Detection Algs.js: -------------------------------------------------------------------------------- 1 | /**** Start of imports. If edited, may not auto-convert in the playground. ****/ 2 | var hdpoly = /* color: #0b4a8b */ee.Geometry.Polygon( 3 | [[[-83.57025146484375, 41.69451375429399], 4 | [-83.3258056640625, 41.66784681529111], 5 | [-83.089599609375, 41.591888125847646], 6 | [-82.9632568359375, 41.505556308050686], 7 | [-82.85064697265625, 41.51172668948318], 8 | [-82.54302978515625, 41.99732721531279], 9 | [-82.606201171875, 42.038137999741075], 10 | [-82.6995849609375, 42.04221763769271], 11 | [-82.82867431640625, 42.00549146736489], 12 | [-82.935791015625, 41.98507887314324], 13 | [-83.03466796875, 42.03609808254534], 14 | [-83.1060791015625, 42.05445497999412], 15 | [-83.1060791015625, 42.14208693993462], 16 | [-83.07861328125, 42.209257234286305], 17 | [-83.11019897460938, 42.26768689318153], 18 | [-83.15963745117188, 42.236175276963394], 19 | [-83.199462890625, 42.09570676103531], 20 | [-83.2708740234375, 41.9421911163313], 21 | [-83.33404541015625, 41.94014812213756], 22 | [-83.3697509765625, 41.8829177113481], 23 | [-83.47137451171875, 41.792880552910205], 24 | [-83.51875305175781, 41.70292500835719]]]); 25 | /***** End of imports. If edited, may not auto-convert in the playground. *****/ 26 | // Comparing algal bloom detection algorithms (Ho et al., 2017) 27 | 28 | // Implements the Automatic Cloud Cover Assessment, with some changes 29 | // http://landsathandbook.gsfc.nasa.gov/pdfs/ACCA_SPIE_paper.pdf 30 | var calcConsACCA = function(img) { 31 | var f1 = img.select('B3').lt(0.08); // B3 below 0.08 is non-cloud 32 | var f2 = img.normalizedDifference(['B2','B5']).gt(0.7); // (B2-B5)/(B2+B5) above 0.7 is non-cloud 33 | var f3 = img.select('B6').gt(300); // B6 above this is non-cloud 34 | 35 | /* Do not implement Pass Two here for simplicity, hence these 36 | estimates are conservative (i.e., all ambiguous is cloud) */ 37 | 38 | var clouds = (f1.or(f2).or(f3)).not(); 39 | 40 | return img.addBands(clouds.rename('cloud')); 41 | }; 42 | 43 | //Specifies a threshold for hue to estimate green pixels 44 | var calcGreenness = function (img) { 45 | var r = img.select(['B3']); 46 | var g = img.select(['B2']); 47 | var b = img.select(['B1']); 48 | var I = r.add(g).add(b).rename(['I']); 49 | var mins = r.min(g).min(b).rename(['mins']); 50 | 51 | var H = mins.where(mins.eq(r), 52 | (b.subtract(r)).divide(I.subtract(r.multiply(3))).add(1) ); 53 | H = H.where(mins.eq(g), 54 | (r.subtract(g)).divide(I.subtract(g.multiply(3))).add(2) ); 55 | H = H.where(mins.eq(b), 56 | (g.subtract(b)).divide(I.subtract(b.multiply(3))) ); 57 | var Hthresh = H.lte(1.6); //threshold of 1.6 fit as described in Ho et al. (2017) 58 | 59 | return Hthresh; 60 | }; 61 | 62 | // Implements the TOA algorithms after correcting for clouds 63 | var calcAlgorithms = function(img) { 64 | var img2 = calcConsACCA(img); 65 | var yesCloud = img2.select("cloud"); 66 | 67 | //add algorithm outputs as bands 68 | var img3 = img2.expression("b('B3')/b('B1')").select(["B3"],["RedToBlue"]); 69 | img3 = img3.addBands(img2.expression("b('B3')/b('B4')").select(['B3'],['RedToNIR']) ); 70 | img3 = img3.addBands(img2.expression("b('B2')/b('B1')").select(['B2'],['GreenToBlue']) ); 71 | img3 = img3.addBands(img2.expression("( b('B1')-b('B3') )/b('B2')") 72 | .select(['B1'],['BlueMinusRedOverGreen']) ); 73 | img3 = img3.addBands(img2.expression("47.7-9.21*(2.9594+1.6203*b('B3raw'))/(2.1572+0.9198*b('B1raw'))+" + 74 | "29.7*(3.5046+0.8950*b('B4raw'))/(2.1572+0.9198*b('B1raw'))-" + 75 | "118*(3.5046+0.8950*b('B4raw'))/(2.9594+1.6203*b('B3raw'))-" + 76 | "6.81*(3.1591+1.0111*b('B5raw'))/(2.9594+1.6203*b('B3raw'))+" + 77 | "41.9*(2.8122+1.3984*b('B7raw'))/(2.9594+1.6203*b('B3raw'))-" + 78 | "14.7*(2.8122+1.3984*b('B7raw'))/(3.5046+0.8950*b('B4raw'))") 79 | .select(['constant'],['PhycocyaninDetection']) ); 80 | img3 = img3.addBands(img2.select('B4').select(['B4'],['NIR']) ); 81 | img3 = img3.addBands(img2.expression("b('B4')-b('B5')").select(['B4'],['NIRwithSAC']) ); 82 | 83 | var img_impnirwithsac = img2.expression("b('B4')-1.03*b('B5')").select(["B4"],["ImprovedNIRwithSAC"]); 84 | var gness=calcGreenness(img); 85 | img_impnirwithsac = img_impnirwithsac.where(gness.eq(0),0); 86 | img3 = img3.addBands(img_impnirwithsac); 87 | 88 | img3=img3.addBands(img2.expression("b('B4') - b('B3')").select(["B4"],["NIRminusRed"])); 89 | img3=img3.addBands(img2.expression("(b('B4')-b('B5'))/(b('B3')-b('B5'))") 90 | .select(["B4"],["NIRoverRedwithSAC"])); 91 | img3=img3.addBands(img2.expression("( b('B4')-" + 92 | "(b('B4')-b('B1'))+(b('B1')-b('B5'))*(850-490)/(1650-490)" + 93 | ") / " + 94 | "( b('B3')-" + 95 | "(b('B3')-b('B1'))+(b('B1')-b('B5'))*(660-490)/(1650-490)" + 96 | ")").select(["B4"],["NIRoverRedwithBAC"]) ); 97 | img3=img3.addBands(img2.expression("(b('B4')-b('B3'))+0.5*(b('B3')-b('B5'))") 98 | .select(["B4"],["CurvatureAroundRed"])); 99 | 100 | var thresholds = ee.Image([0.462, 0.841, 5.93, 0.0327, 0.0277, 0.0235, 0.495]); 101 | var img4=img3.select(['RedToBlue','GreenToBlue','PhycocyaninDetection', 102 | 'NIR','NIRwithSAC','ImprovedNIRwithSAC','NIRoverRedwithSAC']) 103 | img4=img4.gte(thresholds); 104 | 105 | // mask out clouds 106 | img4=img4.mask(yesCloud.not()); 107 | 108 | return (img.addBands(yesCloud).addBands(img4)); 109 | }; 110 | 111 | // Function takes the MERIS_DN band and transforms into CI 112 | var transformMERIS = function(img) { 113 | img = img.rename('MERIS_DN'); 114 | var justCI = img.select('MERIS_DN'); 115 | var land = justCI.eq(252); 116 | var clouds = justCI.eq(253); 117 | 118 | justCI = justCI.mask(justCI.neq(252)); //mask land 119 | 120 | /* convert to CI based on 121 | CI = 10.^((double(DN)-10-1)/(250/2.5)-4) 122 | (itself based on: 123 | DN =1+(250/2.5)*(4+LOG10(CI))+10 124 | see Stumpf et al., 2012 125 | ) 126 | */ 127 | justCI = ee.Image(10).pow( 128 | justCI.double().subtract(10).subtract(1) 129 | .divide(250/2.5) 130 | .subtract(4) 131 | ); 132 | 133 | //See Stumpf et al. 2012 for thresh of 0.001 ~= 10^5 cells/mL 134 | justCI=justCI.gte(0.001); 135 | justCI = justCI.where(clouds,-0.01/6); 136 | 137 | return (img.addBands(justCI.rename('MERIS_CI')) 138 | .addBands(land.rename('landmask'))); 139 | }; 140 | 141 | var L5_TOA = ee.ImageCollection('LANDSAT/LT5_L1T_TOA'); 142 | var L5_DN = ee.ImageCollection('LANDSAT/LT5_L1T') 143 | .select(["B1","B2","B3","B4","B5","B6","B7"], 144 | ["B1raw","B2raw","B3raw","B4raw","B5raw","B6raw","B7raw"]); 145 | var L5_TOA_DN = L5_TOA.combine(L5_DN,true); // Phycocyanin algorithm uses DN, so combine with TOA 146 | 147 | var erieWBcenter = ee.Geometry.Point(-83.0,41.8); 148 | var img = L5_TOA_DN 149 | .filterBounds(erieWBcenter) 150 | .filterDate(ee.Date.fromYMD(2011,9,3).getRange('day')) 151 | .first(); 152 | var img = ee.Image(img); //manual cast so that EE recognizes it as an image 153 | var algs = calcAlgorithms(img); 154 | // Envisat-1 MERIS data provided by European Space Agency 155 | // (http://stanford.edu/~jeffho/images/esa_logo.gif) 156 | var merisDN = ee.Image("users/jeffreyh/MERIS_CI/envisat201124609031543CL3GL1e80CI"); 157 | var merisCI = transformMERIS(merisDN); 158 | 159 | // Default visualization algorithm. 160 | var algorithmToView = 'ImprovedNIRwithSAC' 161 | // one of ['RedToBlue','GreenToBlue','PhycocyaninDetection', 162 | // 'NIR','NIRwithSAC','ImprovedNIRwithSAC','NIRoverRedwithSAC'] 163 | var name_list = [ 164 | 'ImprovedNIRwithSAC','NIRwithSAC','GreenToBlue', 165 | 'NIRoverRedwithSAC','RedToBlue','NIR','PhycocyaninDetection', 166 | ]; 167 | 168 | var land = ee.Image('ESA/GLOBCOVER_L4_200901_200912_V2_3').select('landcover').neq(210); 169 | land = land.clip(img.geometry()); // clip global landcover map to image 170 | algs = algs.mask(land.not()); //mask water 171 | var land_visParams = {min:1, max:1, palette:['3A4A50']}; 172 | var bloom_visParams = {min:0,max:1, palette:['000000','31a354']}; 173 | var agree_visParams = {min: 0, max: 2, palette:['000000','ffffff','ff0000',]}; 174 | var maps = []; 175 | 176 | // Function to switch algorithms 177 | var selectAlg = function(alg_name) { 178 | //Update left map 179 | var alg_img = algs.select(alg_name); 180 | var layer0 = ui.Map.Layer(alg_img,bloom_visParams,'L5 '+alg_name); 181 | maps[0].layers().set(0, ui.Map.Layer(land,land_visParams,'Land')); 182 | maps[0].layers().set(1, layer0); 183 | 184 | //Update middle map 185 | var alg_agree = merisCIclipped.neq(algs.select(alg_name)); 186 | var alg_comm = algs.select(alg_name).gt(merisCIclipped); 187 | alg_agree = alg_agree.where(alg_comm,2); 188 | var layer1 = ui.Map.Layer(alg_agree,agree_visParams,'AgreementOmissionCommission'); 189 | maps[1].layers().set(0, ui.Map.Layer(land,land_visParams,'Land')); 190 | maps[1].layers().set(1, layer1); 191 | 192 | // Update panel with histogram 193 | panel.clear(); 194 | var alg_hist = ui.Chart.image.histogram(alg_agree, hdpoly, 90) 195 | .setSeriesNames([alg_name]) 196 | .setOptions(options); 197 | panel.add(panelText); 198 | panel.add(alg_hist); 199 | panel.add(checkbox); 200 | 201 | }; 202 | 203 | // Map Left - Algorithm to compare 204 | var mapAselect = ui.Select({ 205 | items: name_list, 206 | placeholder: 'ImprovedNIRwithSAC', 207 | style:{width:'200px',textAlign:'center'}, 208 | onChange: selectAlg, 209 | }); 210 | var mapA = ui.Map(); 211 | mapA.add(mapAselect); 212 | mapA.addLayer(land,land_visParams,'Land'); 213 | mapA.addLayer(algs.select(algorithmToView),bloom_visParams,'L5 Improved NIRwithSAC'); 214 | mapA.addLayer(hdpoly,{color:'FFFFFF'},'Western basin boundaries',false,0.5); 215 | mapA.setControlVisibility(false); 216 | print(mapA.layers()) 217 | maps.push(mapA); 218 | 219 | // Map Middle - Agreement vs omission errors vs commission errors 220 | var merisCIclipped = merisCI.select('MERIS_CI').clip(img.geometry()); 221 | var agree = merisCIclipped.neq(algs.select(algorithmToView)); 222 | var commission = algs.select(algorithmToView).gt(merisCIclipped); 223 | agree = agree.where(commission,2); 224 | var mapDiff = ui.Map(); 225 | mapDiff.add(ui.Label( 226 | 'Agreement (black) vs. Omission (white) vs. Commission (red)', 227 | {width:'250px',textAlign:'center'} 228 | )); 229 | mapDiff.addLayer(land,land_visParams,'Land'); 230 | mapDiff.addLayer(agree,agree_visParams,'AgreementOmissionCommission'); 231 | mapDiff.addLayer(hdpoly,{color:'FFFFFF'},'Western basin boundaries',false,0.5); 232 | //red for commission, white for omission, black for agreement 233 | mapDiff.setControlVisibility(false); 234 | print(mapDiff.layers()) 235 | maps.push(mapDiff); 236 | 237 | // Map Right - MERIS 238 | var mapB = ui.Map(); 239 | mapB.add(ui.Label( 240 | 'MERIS CI reference image', 241 | {width:'200px',textAlign:'center'} 242 | )); 243 | mapB.addLayer(merisCI.select('landmask'),land_visParams,'MERIS CI Land'); 244 | mapB.addLayer(merisCI.select('MERIS_CI'),bloom_visParams,'Meris CI lake'); 245 | mapB.addLayer(hdpoly,{color:'FFFFFF'},'Western basin boundaries',false,0.5); 246 | mapB.setControlVisibility(false); 247 | maps.push(mapB); 248 | 249 | var linker = ui.Map.Linker(maps); 250 | 251 | // Enable zooming on the top-left map. 252 | maps[0].setControlVisibility({zoomControl: true}); 253 | 254 | // Show the scale (e.g. '500m') on the bottom-right map. 255 | maps[2].setControlVisibility({scaleControl: true}); 256 | 257 | // Create a title. 258 | var title = ui.Label('Comparing algal bloom detection algorithms (Ho et al., 2017)', { 259 | stretch: 'horizontal', 260 | textAlign: 'center', 261 | fontWeight: 'bold', 262 | fontSize: '24px' 263 | }); 264 | 265 | // Add a left panel to show comparison statistics 266 | var panel = ui.Panel({style: {width: '250px'}}); 267 | // Add a label. 268 | var panelText = ui.Label('Comparison statistics vs. MERIS CI over the western basin:'); 269 | panel.add(panelText); 270 | // Add a checkbox showing summary area 271 | var checkbox = ui.Checkbox('Show western basin boundaries', false); 272 | checkbox.onChange(function(checked) { 273 | maps[0].layers().get(2).setShown(checked); 274 | maps[1].layers().get(2).setShown(checked); 275 | maps[2].layers().get(2).setShown(checked); 276 | }); 277 | // Add a bar chart for Map diff 278 | var options = { 279 | fontSize: 10, 280 | hAxis: { 281 | // title:'Agreement/Omission/Commission', 282 | ticks: [{v: 0, f: 'Agreement'}, 283 | {v: 1, f: 'Omission'}, 284 | {v: 2, f: 'Commission'}] 285 | }, 286 | vAxis: {title: 'Number of pixels'}, 287 | colors: ['31a354'], 288 | // colors: ['#000000','#0000ff','#ff0000'], 289 | }; 290 | // Make the histogram, set the options. 291 | var histogram = ui.Chart.image.histogram(agree, hdpoly, 90) 292 | .setSeriesNames([algorithmToView]) 293 | .setOptions(options); 294 | // I tried to do this to have the histogram colors match the middle image, 295 | // but doing so seemed to mess up the ticks labels, so I left the bars 296 | // blue. 297 | // var agree_vis = agree.mask(agree.eq(0)); 298 | // agree_vis = agree_vis.addBands(agree.mask(agree.eq(1))); 299 | // agree_vis = agree_vis.addBands(agree.mask(agree.eq(2))); 300 | // agree_vis = agree_vis.rename(['Agreement','Omission','Comission']) 301 | // var histogram = ui.Chart.image.histogram(agree_vis, hdpoly, 90) 302 | // .setSeriesNames(['Selected Alg vs. MERIS CI']) 303 | // .setOptions(options); 304 | 305 | panel.add(histogram); 306 | panel.add(checkbox); 307 | 308 | // Create a grid of maps. 309 | var mapGrid = ui.Panel([ 310 | panel, 311 | ui.Panel([maps[0]], null, {stretch: 'both'}), 312 | ui.Panel([maps[1]], null, {stretch: 'both'}), 313 | ui.Panel([maps[2]], null, {stretch: 'both'}), 314 | ], 315 | ui.Panel.Layout.Flow('horizontal'), {stretch: 'both'} 316 | ); 317 | 318 | // Add the maps and title to the ui.root. 319 | ui.root.widgets().reset([title, mapGrid]); 320 | ui.root.setLayout(ui.Panel.Layout.Flow('vertical')); 321 | 322 | // Center the maps at Lake Erie. 323 | maps[0].centerObject(erieWBcenter,8); 324 | --------------------------------------------------------------------------------