Skip to content

Commit 9368db5

Browse files
Revert "(21/24) fix for NPW was already on master"
This reverts commit 3cbf6f3.
1 parent e2fd9ae commit 9368db5

File tree

6 files changed

+111
-272
lines changed

6 files changed

+111
-272
lines changed

idepix-olci/pom.xml

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -99,13 +99,15 @@
9999
</dependency>
100100
<dependency>
101101
<groupId>org.esa.s3tbx</groupId>
102-
<artifactId>s3tbx-olci-radiometry</artifactId>
102+
<artifactId>s3tbx-olci-o2aharmonisation</artifactId>
103103
<version>${s3tbx.version}</version>
104104
</dependency>
105+
105106
<dependency>
106-
<groupId>org.esa.s3tbx</groupId>
107-
<artifactId>s3tbx-olci-o2aharmonisation</artifactId>
108-
<version>${s3tbx.version}</version>
107+
<groupId>org.mockito</groupId>
108+
<artifactId>mockito-all</artifactId>
109+
<version>1.9.5</version>
110+
<scope>test</scope>
109111
</dependency>
110112

111113
<dependency>
@@ -126,12 +128,6 @@
126128
<version>1.5.0</version>
127129
</dependency>
128130

129-
<dependency>
130-
<groupId>org.mockito</groupId>
131-
<artifactId>mockito-all</artifactId>
132-
<version>1.9.5</version>
133-
<scope>test</scope>
134-
</dependency>
135131
<dependency>
136132
<groupId>junit</groupId>
137133
<artifactId>junit</artifactId>

idepix-olci/src/main/java/org/esa/snap/idepix/olci/IdepixOlciClassificationOp.java

Lines changed: 98 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import org.esa.s3tbx.processor.rad2refl.Rad2ReflConstants;
55
import org.esa.snap.core.datamodel.Band;
66
import org.esa.snap.core.datamodel.FlagCoding;
7+
import org.esa.snap.core.datamodel.GeoCoding;
78
import org.esa.snap.core.datamodel.GeoPos;
89
import org.esa.snap.core.datamodel.Product;
910
import org.esa.snap.core.datamodel.ProductData;
@@ -22,6 +23,7 @@
2223
import org.esa.snap.idepix.core.util.IdepixIO;
2324
import org.esa.snap.idepix.core.util.IdepixUtils;
2425
import org.esa.snap.idepix.core.util.SchillerNeuralNetWrapper;
26+
import org.esa.snap.watermask.operator.WatermaskClassifier;
2527
import org.locationtech.jts.geom.Coordinate;
2628
import org.locationtech.jts.geom.GeometryFactory;
2729
import org.locationtech.jts.geom.Polygon;
@@ -34,6 +36,8 @@
3436
import java.util.Calendar;
3537
import java.util.Map;
3638

39+
import static org.esa.snap.idepix.core.IdepixConstants.*;
40+
3741
/**
3842
* OLCI pixel classification operator.
3943
* Processes both land and water, so we get rid of separate land/water merge operator.
@@ -62,13 +66,13 @@
6266
public class IdepixOlciClassificationOp extends Operator {
6367

6468
@Parameter(defaultValue = "false",
65-
label = "Write NN value to the target product.",
66-
description = "If applied, write Schiller NN value to the target product ")
69+
label = " Write NN value to the target product.",
70+
description = " If applied, write Schiller NN value to the target product ")
6771
private boolean outputSchillerNNValue;
6872

6973
@Parameter(description = "Alternative NN file. " +
7074
"If set, it MUST follow format and input/output as used in default '11x10x4x3x2_207.9.net. ",
71-
label = "Alternative NN file")
75+
label = " Alternative NN file")
7276
private File alternativeNNFile;
7377

7478
@Parameter(defaultValue = "false",
@@ -77,6 +81,13 @@ public class IdepixOlciClassificationOp extends Operator {
7781
)
7882
private boolean ignoreSeaIceClimatology;
7983

84+
@Parameter(defaultValue = "false",
85+
label = " Use SRTM Land/Water mask",
86+
description = "If selected, SRTM Land/Water mask is used instead of L1b land flag. " +
87+
"Slower, but in general more precise.")
88+
private boolean useSrtmLandWaterMask;
89+
90+
8091
@SourceProduct(alias = "l1b", description = "The L1b product.")
8192
private Product l1bProduct;
8293

@@ -91,14 +102,10 @@ public class IdepixOlciClassificationOp extends Operator {
91102
@SourceProduct(alias = "o2Corr", optional = true)
92103
private Product o2CorrProduct;
93104

94-
@SourceProduct(alias = "rBRR", optional = true)
95-
private Product rBRRProduct;
96-
97105
@TargetProduct(description = "The target product.")
98106
Product targetProduct;
99107

100108
private Band[] olciReflBands;
101-
private Band[] rBRRBands;
102109

103110
private Band surface13Band;
104111
private Band trans13Band;
@@ -119,17 +126,26 @@ public class IdepixOlciClassificationOp extends Operator {
119126
private GeometryFactory gf;
120127
private Polygon arcticPolygon;
121128
private Polygon antarcticaPolygon;
129+
private WatermaskClassifier watermaskClassifier;
122130

123131
private LakeSeaIceClassification lakeSeaIceClassification;
124132

125133

126134
@Override
127135
public void initialize() throws OperatorException {
128136
setBands();
129-
setrBRRBands();
130137
nnInterpreter = IdepixOlciCloudNNInterpreter.create();
131138
readSchillerNeuralNets();
132139
createTargetProduct();
140+
if (useSrtmLandWaterMask) {
141+
try {
142+
watermaskClassifier = new WatermaskClassifier(LAND_WATER_MASK_RESOLUTION,
143+
OVERSAMPLING_FACTOR_X,
144+
OVERSAMPLING_FACTOR_Y);
145+
} catch (IOException e) {
146+
throw new OperatorException("Could not initialise SRTM land-water mask", e);
147+
}
148+
}
133149

134150
initLakeSeaIceClassification();
135151

@@ -177,14 +193,6 @@ private void setBands() {
177193
}
178194
}
179195

180-
private void setrBRRBands() {
181-
rBRRBands = new Band[5];
182-
String[] rBRRBandname = new String[]{"01", "04", "06", "08", "17"};
183-
for (int i = 0; i < rBRRBandname.length; i++) {
184-
rBRRBands[i] = rBRRProduct.getBand("rBRR_" + rBRRBandname[i]);
185-
}
186-
}
187-
188196
private void createTargetProduct() throws OperatorException {
189197
int sceneWidth = l1bProduct.getSceneRasterWidth();
190198
int sceneHeight = l1bProduct.getSceneRasterHeight();
@@ -226,111 +234,61 @@ public void computeTileStack(Map<Band, Tile> targetTiles, Rectangle rectangle, P
226234
olciReflectanceTiles[i] = getSourceTile(olciReflBands[i], rectangle);
227235
}
228236

229-
Tile[] rBRRTiles = new Tile[rBRRBands.length];
230-
for (int i = 0; i < rBRRBands.length; i++) {
231-
rBRRTiles[i] = getSourceTile(rBRRBands[i], rectangle);
232-
}
233-
234-
235237
final Band cloudFlagTargetBand = targetProduct.getBand(IdepixConstants.CLASSIF_BAND_NAME);
236238
final Tile cloudFlagTargetTile = targetTiles.get(cloudFlagTargetBand);
237239

240+
238241
Tile nnTargetTile = null;
239242
if (outputSchillerNNValue) {
240243
nnTargetTile = targetTiles.get(targetProduct.getBand(IdepixConstants.NN_OUTPUT_BAND_NAME));
241244
}
242245
try {
246+
GeoCoding geoCoding = l1bProduct.getSceneGeoCoding();
243247
for (int y = rectangle.y; y < rectangle.y + rectangle.height; y++) {
244248
checkForCancellation();
245249
for (int x = rectangle.x; x < rectangle.x + rectangle.width; x++) {
246-
247-
final boolean coastalZone = isCoastalZone(olciQualityFlagTile, x, y);
248-
cloudFlagTargetTile.setSample(x, y, IdepixConstants.IDEPIX_COASTLINE, coastalZone);
249-
250-
final boolean isStaticLand = olciQualityFlagTile.getSampleBit(x, y, IdepixOlciConstants.L1_F_LAND) &&
251-
!olciQualityFlagTile.getSampleBit(x, y, IdepixOlciConstants.L1_F_FRESH_INLAND_WATER);
252-
cloudFlagTargetTile.setSample(x, y, IdepixConstants.IDEPIX_LAND, isStaticLand);
253-
254-
boolean isLAND;
255-
if (coastalZone) {
256-
isLAND = isOLCILandUpdatedInCoastalZone(x, y, isStaticLand, olciQualityFlagTile, rBRRTiles);
257-
} else {
258-
isLAND = cloudFlagTargetTile.getSampleBit(x, y, IdepixConstants.IDEPIX_LAND);
250+
int waterFraction = -1;
251+
if (useSrtmLandWaterMask) {
252+
waterFraction = watermaskClassifier.getWaterMaskFraction(geoCoding, x, y);
259253
}
260254

261255
initCloudFlag(olciQualityFlagTile, targetTiles.get(cloudFlagTargetBand), olciReflectanceTiles, y, x);
262256
final boolean isBright = olciQualityFlagTile.getSampleBit(x, y, IdepixOlciConstants.L1_F_BRIGHT);
263257
cloudFlagTargetTile.setSample(x, y, IdepixConstants.IDEPIX_BRIGHT, isBright);
258+
final boolean isCoastlineFromAppliedMask = classifyCoastline(olciQualityFlagTile, x, y, waterFraction);
259+
cloudFlagTargetTile.setSample(x, y, IdepixConstants.IDEPIX_COASTLINE, isCoastlineFromAppliedMask);
260+
261+
final boolean isLandFromAppliedMask = isOlciLandPixel(x, y, olciQualityFlagTile, waterFraction);
262+
final boolean isInlandWaterFromAppliedMask = isOlciInlandWaterPixel(x, y, olciQualityFlagTile, waterFraction);
263+
//todo: for CGLOPS, coastlines are added to LAND to exclude them from L2 processing
264+
cloudFlagTargetTile.setSample(x, y, IdepixConstants.IDEPIX_LAND, isLandFromAppliedMask ||
265+
isCoastlineFromAppliedMask);
264266

265-
if (isLAND) {
267+
// todo: for cglops, coastlines are treated as LAND
268+
if ((isLandFromAppliedMask && !isInlandWaterFromAppliedMask) || isCoastlineFromAppliedMask) {
266269
classifyOverLand(olciReflectanceTiles, cloudFlagTargetTile, nnTargetTile,
267270
surface13Tile, trans13Tile, x, y);
268271
} else {
269-
// test on ambiguous clouds in inland water remains in the classification
270-
boolean isProcessableWater = classifyOverWater(olciQualityFlagTile, olciReflectanceTiles,
271-
cloudFlagTargetTile, nnTargetTile, y, x);
272-
cloudFlagTargetTile.setSample(x, y, IdepixOlciConstants.IDEPIX_WATER_PROCESSABLE, isProcessableWater);
272+
classifyOverWater(olciQualityFlagTile, olciReflectanceTiles,
273+
cloudFlagTargetTile, nnTargetTile, x, y, isInlandWaterFromAppliedMask);
273274
}
274-
275275
}
276276
}
277277
} catch (Exception e) {
278278
throw new OperatorException("Failed to provide GA cloud screening:\n" + e.getMessage(), e);
279279
}
280280
}
281281

282-
private boolean isOLCILandUpdatedInCoastalZone(int x, int y, boolean staticLand, Tile sourceFlagTile, Tile[] rBRRTiles) {
283-
// LAND_updated = (staticLand and not coastline) or (staticLand and coastline and NDVI>0.07 and not quality_flags.bright) or ((coastline and staticLand and quality_flags.bright))
284-
// LAND from Water: (staticLand==0 and coastline and not quality_flags.bright) and NDVI>0.07
285-
// Bright beach: (rBRR_06 - rBRR_04)/(560-490) > 0 and (rBRR_04 - rBRR_01)/(490-400) > 0 and ( coastline) and rBRR_06 > 0.1
286-
287-
Rectangle rectangle = sourceFlagTile.getRectangle();
288-
if (rectangle.contains(x, y) && !sourceFlagTile.getSampleBit(x, y, IdepixOlciConstants.L1_F_INVALID)) {
289-
float rBrr0 = rBRRTiles[0].getSampleFloat(x, y);
290-
float rBrr1 = rBRRTiles[1].getSampleFloat(x, y);
291-
float rBrr2 = rBRRTiles[2].getSampleFloat(x, y);
292-
float rBrr3 = rBRRTiles[3].getSampleFloat(x, y);
293-
float rBrr4 = rBRRTiles[4].getSampleFloat(x, y);
294-
295-
float ndvi = (rBrr4 - rBrr3) / (rBrr4 + rBrr3);
296-
boolean isBright = sourceFlagTile.getSampleBit(x, y, IdepixOlciConstants.L1_F_BRIGHT);
297-
298-
boolean isLandUpdated = (staticLand && ndvi > 0.07 && !isBright) || (staticLand && isBright);
299-
boolean isLandFromWater = (!staticLand && !isBright) && ndvi > 0.07;
300-
boolean isBrightBeach = (rBrr2 - rBrr1) / (560 - 490.) > 0 &&
301-
(rBrr1 - rBrr0) / (490 - 400) > 0 &&
302-
rBrr2 > 0.1 && ndvi > 0.;
303-
304-
return isLandUpdated || isLandFromWater || isBrightBeach;
305-
} else {
306-
return staticLand;
307-
}
282+
private boolean classifyCoastline(Tile olciQualityFlagTile, int x, int y, int waterFraction) {
283+
return waterFraction < 0 ?
284+
olciQualityFlagTile.getSampleBit(x, y, IdepixOlciConstants.L1_F_COASTLINE) :
285+
isCoastlinePixel(x, y, waterFraction);
308286
}
309287

310-
private boolean isCoastalZone(Tile sourceFlagTile, int x, int y) {
311-
int pixelCount = 0;
312-
int sizeRectangle = 0;
313-
Rectangle rectangle = sourceFlagTile.getRectangle();
314-
int buffer = 3; //default value 3 for dilation 7x7
315-
for (int i = x - buffer; i <= x + buffer; i++) {
316-
for (int j = y - buffer; j <= y + buffer; j++) {
317-
if (rectangle.contains(i, j)) {
318-
sizeRectangle++;
319-
if (sourceFlagTile.getSampleBit(i, j, IdepixOlciConstants.L1_F_LAND) &&
320-
!sourceFlagTile.getSampleBit(i, j, IdepixOlciConstants.L1_F_FRESH_INLAND_WATER)) {
321-
pixelCount++;
322-
}
323-
}
288+
private void classifyOverWater(Tile olciQualityFlagTile, Tile[] olciReflectanceTiles,
289+
Tile cloudFlagTargetTile, Tile nnTargetTile, int x, int y, boolean isInlandWater) {
324290

325-
}
326-
}
327-
return (pixelCount > 0 && pixelCount < sizeRectangle);
328-
}
329291

330-
private boolean classifyOverWater(Tile olciQualityFlagTile, Tile[] olciReflectanceTiles,
331-
Tile cloudFlagTargetTile, Tile nnTargetTile, int y, int x) {
332-
333-
boolean isProcessableWater = true;
334292
double nnOutput = getOlciNNOutput(x, y, olciReflectanceTiles);
335293
if (!cloudFlagTargetTile.getSampleBit(x, y, IdepixConstants.IDEPIX_INVALID)) {
336294
cloudFlagTargetTile.setSample(x, y, IdepixConstants.IDEPIX_CLOUD_AMBIGUOUS, false);
@@ -357,29 +315,25 @@ private boolean classifyOverWater(Tile olciQualityFlagTile, Tile[] olciReflectan
357315
cloudFlagTargetTile.setSample(x, y, IdepixConstants.IDEPIX_CLOUD, false);
358316
}
359317

360-
final boolean isInlandWater = olciQualityFlagTile.getSampleBit(x, y, IdepixOlciConstants.L1_F_LAND);
361-
362318
if (isInlandWater && cloudAmbiguous) {
363319
final double NDVI = getNDVI(x, y, olciReflectanceTiles);
364320
if (NDVI > 0.07) {
365321
//catches mixed pixels at coast lines.
366322
cloudFlagTargetTile.setSample(x, y, IdepixConstants.IDEPIX_CLOUD_AMBIGUOUS, false);
367323
cloudFlagTargetTile.setSample(x, y, IdepixConstants.IDEPIX_CLOUD, false);
368-
isProcessableWater = false;
324+
// todo: for CGLOPS it is OK, if these mixed pixels are classified as LAND!
325+
cloudFlagTargetTile.setSample(x, y, IDEPIX_LAND, true);
369326

370327
}
371328
if (olciQualityFlagTile.getSampleBit(x, y, IdepixOlciConstants.L1_F_BRIGHT)) {
372329
cloudFlagTargetTile.setSample(x, y, IdepixConstants.IDEPIX_CLOUD_AMBIGUOUS, true);
373330
cloudFlagTargetTile.setSample(x, y, IdepixConstants.IDEPIX_CLOUD, true);
374331
}
375332
}
376-
377-
378333
}
379334
if (outputSchillerNNValue) {
380335
nnTargetTile.setSample(x, y, nnOutput);
381336
}
382-
return isProcessableWater;
383337
}
384338

385339
private void classifyOverLand(Tile[] olciReflectanceTiles,
@@ -405,6 +359,8 @@ private void classifyOverLand(Tile[] olciReflectanceTiles,
405359

406360
boolean isSnowIce = nnInterpreter.isSnowIce(nnOutput);
407361

362+
// cloud over snow from harmonisation approach:
363+
// pixel_classif_flags.IDEPIX_LAND && ((Oa21_reflectance > 0.5 && surface_13 - trans_13 < 0.01) || Oa21_reflectance > 0.76)
408364
double surface13;
409365
double trans13;
410366
if (surface13Tile != null && trans13Tile != null) {
@@ -444,6 +400,50 @@ private void classifyOverLand(Tile[] olciReflectanceTiles,
444400
}
445401
}
446402

403+
private boolean isOlciLandPixel(int x, int y, Tile olciL1bFlagTile, int waterFraction) {
404+
if (waterFraction < 0) {
405+
return olciL1bFlagTile.getSampleBit(x, y, IdepixOlciConstants.L1_F_LAND);
406+
} else {
407+
// the water mask ends at 59 Degree south, stop earlier to avoid artefacts
408+
if (IdepixUtils.getGeoPos(l1bProduct.getSceneGeoCoding(), x, y).lat > -58f) {
409+
// values bigger than 100 indicate no data
410+
if (waterFraction <= 100) {
411+
// todo: this does not work if we have a PixelGeocoding. In that case, waterFraction
412+
// is always 0 or 100!! (TS, OD, 20140502)
413+
return waterFraction == 0;
414+
} else {
415+
return olciL1bFlagTile.getSampleBit(x, y, IdepixOlciConstants.L1_F_LAND);
416+
}
417+
} else {
418+
return olciL1bFlagTile.getSampleBit(x, y, IdepixOlciConstants.L1_F_LAND);
419+
}
420+
}
421+
}
422+
423+
private boolean isOlciInlandWaterPixel(int x, int y, Tile olciL1bFlagTile, int waterFraction) {
424+
if (waterFraction < 0) {
425+
// SRTM has not been used! Rely on OLCI flags!
426+
return olciL1bFlagTile.getSampleBit(x, y, IdepixOlciConstants.L1_F_LAND) &&
427+
olciL1bFlagTile.getSampleBit(x, y, IdepixOlciConstants.L1_F_FRESH_INLAND_WATER);
428+
} else {
429+
// SRTM water mask is used.
430+
// the water mask ends at 59 Degree south, stop earlier to avoid artefacts
431+
if (IdepixUtils.getGeoPos(l1bProduct.getSceneGeoCoding(), x, y).lat > -58f) {
432+
// values bigger than 100 indicate no data
433+
if (waterFraction <= 100) {
434+
// todo: this does not work if we have a PixelGeocoding. In that case, waterFraction
435+
// is always 0 or 100!! (TS, OD, 20140502)
436+
return waterFraction > 0 && olciL1bFlagTile.getSampleBit(x, y, IdepixOlciConstants.L1_F_LAND);
437+
} else {
438+
return olciL1bFlagTile.getSampleBit(x, y, IdepixOlciConstants.L1_F_LAND) &&
439+
olciL1bFlagTile.getSampleBit(x, y, IdepixOlciConstants.L1_F_FRESH_INLAND_WATER);
440+
}
441+
} else {
442+
return olciL1bFlagTile.getSampleBit(x, y, IdepixOlciConstants.L1_F_LAND) &&
443+
olciL1bFlagTile.getSampleBit(x, y, IdepixOlciConstants.L1_F_FRESH_INLAND_WATER);
444+
}
445+
}
446+
}
447447

448448
private double getOlciNNOutput(int x, int y, Tile[] rhoToaTiles) {
449449
SchillerNeuralNetWrapper nnWrapper;
@@ -502,6 +502,9 @@ private void initCloudFlag(Tile olciL1bFlagTile, Tile targetTile, Tile[] olciRef
502502
targetTile.setSample(x, y, IdepixConstants.IDEPIX_SNOW_ICE, false);
503503
targetTile.setSample(x, y, IdepixConstants.IDEPIX_CLOUD_BUFFER, false);
504504
targetTile.setSample(x, y, IdepixConstants.IDEPIX_CLOUD_SHADOW, false);
505+
targetTile.setSample(x, y, IdepixConstants.IDEPIX_COASTLINE, false);
506+
targetTile.setSample(x, y, IdepixConstants.IDEPIX_LAND, false);
507+
targetTile.setSample(x, y, IdepixConstants.IDEPIX_BRIGHT, false);
505508
}
506509

507510
/**

idepix-olci/src/main/java/org/esa/snap/idepix/olci/IdepixOlciConstants.java

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,6 @@ public class IdepixOlciConstants {
1919

2020
public static final int IDEPIX_MOUNTAIN_SHADOW = IdepixConstants.NUM_DEFAULT_FLAGS; // first non-default flag
2121
static final String IDEPIX_MOUNTAIN_SHADOW_DESCR_TEXT = "Pixel is affected by a mountain/hill shadow";
22-
static final int IDEPIX_WATER_PROCESSABLE = IdepixConstants.NUM_DEFAULT_FLAGS + 1;
23-
static final String IDEPIX_WATER_PROCESSABLE_DESCR_TEXT = "water that can be processed. Static mask + radiometric tests";
24-
2522

2623
public static final String OLCI_ALTITUDE_BAND_NAME = "altitude";
2724
public static final String OLCI_LATITUDE_BAND_NAME = "latitude";

0 commit comments

Comments
 (0)