Skip to content

Commit 31b9bc3

Browse files
committed
started new module for SLSTR support
1 parent 31a5d5a commit 31b9bc3

36 files changed

+4638
-5
lines changed
Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
org.esa.snap.idepix.olcislstr.OlciSlstrOp$Spi
2-
org.esa.snap.idepix.olcislstr.OlciSlstrCtpOp$Spi
3-
org.esa.snap.idepix.olcislstr.OlciSlstrClassificationOp$Spi
4-
org.esa.snap.idepix.olcislstr.OlciSlstrPostProcessOp$Spi
1+
org.esa.snap.idepix.olcislstr.SlstrOp$Spi
2+
org.esa.snap.idepix.olcislstr.SlstrCtpOp$Spi
3+
org.esa.snap.idepix.olcislstr.SlstrClassificationOp$Spi
4+
org.esa.snap.idepix.olcislstr.SlstrPostProcessOp$Spi

idepix-slstr/pom.xml

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
<!--
2+
~ Copyright (C) 2019 Brockmann Consult GmbH ([email protected])
3+
~
4+
~ This program is free software; you can redistribute it and/or modify it
5+
~ under the terms of the GNU General Public License as published by the Free
6+
~ Software Foundation; either version 3 of the License, or (at your option)
7+
~ any later version.
8+
~ This program is distributed in the hope that it will be useful, but WITHOUT
9+
~ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
10+
~ FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
11+
~ more details.
12+
~
13+
~ You should have received a copy of the GNU General Public License along
14+
~ with this program; if not, see http://www.gnu.org/licenses/
15+
-->
16+
<project xmlns="http://maven.apache.org/POM/4.0.0"
17+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
18+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
19+
20+
<modelVersion>4.0.0</modelVersion>
21+
<parent>
22+
<groupId>org.esa.snap</groupId>
23+
<artifactId>snap-idepix</artifactId>
24+
<version>12.0.0-SNAPSHOT</version>
25+
</parent>
26+
27+
<artifactId>idepix-slstr</artifactId>
28+
<version>12.0.0-SNAPSHOT</version>
29+
30+
<packaging>nbm</packaging>
31+
32+
<name>IdePix Slstr</name>
33+
<description>
34+
Classification of pixels (cloud, snow, ice, land, water) originating from SLSTR L1b products.
35+
</description>
36+
37+
<properties>
38+
<idepix-core.version>12.0.0-SNAPSHOT</idepix-core.version>
39+
<opttbx.version>12.0.0-SNAPSHOT</opttbx.version>
40+
</properties>
41+
42+
<dependencies>
43+
<dependency>
44+
<groupId>org.esa.snap</groupId>
45+
<artifactId>idepix-core</artifactId>
46+
<version>${idepix-core.version}</version>
47+
</dependency>
48+
49+
<dependency>
50+
<groupId>org.esa.snap</groupId>
51+
<artifactId>ceres-core</artifactId>
52+
</dependency>
53+
<dependency>
54+
<groupId>org.esa.snap</groupId>
55+
<artifactId>ceres-binding</artifactId>
56+
</dependency>
57+
<dependency>
58+
<groupId>org.esa.snap</groupId>
59+
<artifactId>ceres-jai</artifactId>
60+
</dependency>
61+
<dependency>
62+
<groupId>org.esa.snap</groupId>
63+
<artifactId>snap-runtime</artifactId>
64+
<version>${snap.version}</version>
65+
</dependency>
66+
<dependency>
67+
<groupId>org.esa.snap</groupId>
68+
<artifactId>snap-core</artifactId>
69+
<version>${snap.version}</version>
70+
</dependency>
71+
<dependency>
72+
<groupId>org.esa.snap</groupId>
73+
<artifactId>snap-gpf</artifactId>
74+
<version>${snap.version}</version>
75+
</dependency>
76+
<dependency>
77+
<groupId>org.esa.snap</groupId>
78+
<artifactId>snap-envisat-reader</artifactId>
79+
<version>${snap.version}</version>
80+
</dependency>
81+
<dependency>
82+
<groupId>org.esa.snap</groupId>
83+
<artifactId>snap-watermask</artifactId>
84+
</dependency>
85+
<dependency>
86+
<groupId>org.esa.snap</groupId>
87+
<artifactId>snap-csv-dataio</artifactId>
88+
<version>${snap.version}</version>
89+
</dependency>
90+
<dependency>
91+
<groupId>org.esa.snap</groupId>
92+
<artifactId>snap-raster</artifactId>
93+
<version>${snap.version}</version>
94+
</dependency>
95+
96+
<dependency>
97+
<groupId>eu.esa.opt</groupId>
98+
<artifactId>opttbx-rad2refl</artifactId>
99+
<version>${opttbx.version}</version>
100+
</dependency>
101+
<dependency>
102+
<groupId>eu.esa.opt</groupId>
103+
<artifactId>opttbx-olci-o2aharmonisation</artifactId>
104+
<version>${opttbx.version}</version>
105+
</dependency>
106+
107+
<dependency>
108+
<groupId>org.mockito</groupId>
109+
<artifactId>mockito-all</artifactId>
110+
<version>1.9.5</version>
111+
<scope>test</scope>
112+
</dependency>
113+
114+
<dependency>
115+
<groupId>com.googlecode.json-simple</groupId>
116+
<artifactId>json-simple</artifactId>
117+
<version>1.1.1</version>
118+
</dependency>
119+
120+
<dependency>
121+
<groupId>org.tensorflow</groupId>
122+
<artifactId>tensorflow</artifactId>
123+
<version>1.15.0</version>
124+
</dependency>
125+
126+
<dependency>
127+
<groupId>com.github.haifengl</groupId>
128+
<artifactId>smile-core</artifactId>
129+
<version>1.5.0</version>
130+
</dependency>
131+
132+
<dependency>
133+
<groupId>junit</groupId>
134+
<artifactId>junit</artifactId>
135+
</dependency>
136+
137+
138+
139+
</dependencies>
140+
141+
<build>
142+
<plugins>
143+
<plugin>
144+
<groupId>org.apache.netbeans.utilities</groupId>
145+
<artifactId>nbm-maven-plugin</artifactId>
146+
</plugin>
147+
148+
</plugins>
149+
</build>
150+
151+
</project>
Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
package org.esa.snap.idepix.slstr;
2+
3+
import com.bc.ceres.core.ProgressMonitor;
4+
import org.esa.snap.idepix.core.IdepixConstants;
5+
import org.esa.snap.idepix.core.util.IdepixIO;
6+
import org.esa.snap.idepix.core.util.IdepixUtils;
7+
import org.esa.snap.idepix.core.util.SchillerNeuralNetWrapper;
8+
import eu.esa.opt.processor.rad2refl.Rad2ReflConstants;
9+
import org.esa.snap.core.datamodel.*;
10+
import org.esa.snap.core.gpf.Operator;
11+
import org.esa.snap.core.gpf.OperatorException;
12+
import org.esa.snap.core.gpf.OperatorSpi;
13+
import org.esa.snap.core.gpf.Tile;
14+
import org.esa.snap.core.gpf.annotations.OperatorMetadata;
15+
import org.esa.snap.core.gpf.annotations.Parameter;
16+
import org.esa.snap.core.gpf.annotations.SourceProduct;
17+
import org.esa.snap.core.gpf.annotations.TargetProduct;
18+
import org.esa.snap.core.util.ProductUtils;
19+
20+
import java.awt.*;
21+
import java.io.InputStream;
22+
import java.util.Map;
23+
24+
/**
25+
* OLCI/SLSTR synergy pixel classification operator.
26+
*
27+
* @author olafd
28+
*/
29+
@OperatorMetadata(alias = "Idepix.Slstr.Classification",
30+
version = "3.0",
31+
internal = true,
32+
authors = "Olaf Danne",
33+
copyright = "(c) 2025 by Brockmann Consult",
34+
description = "IdePix pixel classification operator for SLSTR synergy.")
35+
public class SlstrClassificationOp extends Operator {
36+
37+
@Parameter(defaultValue = "false",
38+
label = " Write NN value to the target product.",
39+
description = " If applied, write Schiller NN value to the target product ")
40+
private boolean outputSchillerNNValue;
41+
42+
@SourceProduct(alias = "l1b", description = "The source product.")
43+
Product sourceProduct;
44+
45+
@SourceProduct(alias = "reflOlci")
46+
private Product olciRad2reflProduct;
47+
48+
@SourceProduct(alias = "waterMask")
49+
private Product waterMaskProduct;
50+
51+
52+
@TargetProduct(description = "The target product.")
53+
Product targetProduct;
54+
55+
Band cloudFlagBand;
56+
57+
private Band[] olciReflBands;
58+
59+
private Band landWaterBand;
60+
61+
public static final String OLCISLSTR_ALL_NET_NAME = "11x10x4x3x2_207.9.net";
62+
63+
private static final double THRESH_LAND_MINBRIGHT1 = 0.3;
64+
private static final double THRESH_LAND_MINBRIGHT2 = 0.25; // test OD 20170411
65+
66+
ThreadLocal<SchillerNeuralNetWrapper> olciSlstrAllNeuralNet;
67+
private SlstrCloudNNInterpreter nnInterpreter;
68+
69+
70+
@Override
71+
public void initialize() throws OperatorException {
72+
setBands();
73+
nnInterpreter = SlstrCloudNNInterpreter.create();
74+
readSchillerNeuralNets();
75+
createTargetProduct();
76+
}
77+
78+
private void readSchillerNeuralNets() {
79+
InputStream olciAllIS = getClass().getResourceAsStream(OLCISLSTR_ALL_NET_NAME);
80+
olciSlstrAllNeuralNet = SchillerNeuralNetWrapper.create(olciAllIS);
81+
}
82+
83+
public void setBands() {
84+
// e.g. Oa07_reflectance
85+
olciReflBands = new Band[Rad2ReflConstants.OLCI_REFL_BAND_NAMES.length];
86+
for (int i = 0; i < Rad2ReflConstants.OLCI_REFL_BAND_NAMES.length; i++) {
87+
final int suffixStart = Rad2ReflConstants.OLCI_REFL_BAND_NAMES[i].indexOf("_");
88+
final String reflBandname = Rad2ReflConstants.OLCI_REFL_BAND_NAMES[i].substring(0, suffixStart);
89+
olciReflBands[i] = olciRad2reflProduct.getBand(reflBandname + "_reflectance");
90+
}
91+
92+
landWaterBand = waterMaskProduct.getBand("land_water_fraction");
93+
}
94+
95+
void createTargetProduct() throws OperatorException {
96+
int sceneWidth = sourceProduct.getSceneRasterWidth();
97+
int sceneHeight = sourceProduct.getSceneRasterHeight();
98+
99+
targetProduct = new Product(sourceProduct.getName(), sourceProduct.getProductType(), sceneWidth, sceneHeight);
100+
101+
// shall be the only target band!!
102+
cloudFlagBand = targetProduct.addBand(IdepixConstants.CLASSIF_BAND_NAME, ProductData.TYPE_INT16);
103+
FlagCoding flagCoding = SlstrUtils.createOlciFlagCoding();
104+
cloudFlagBand.setSampleCoding(flagCoding);
105+
targetProduct.getFlagCodingGroup().add(flagCoding);
106+
107+
ProductUtils.copyTiePointGrids(sourceProduct, targetProduct);
108+
109+
ProductUtils.copyGeoCoding(sourceProduct, targetProduct);
110+
targetProduct.setStartTime(sourceProduct.getStartTime());
111+
targetProduct.setEndTime(sourceProduct.getEndTime());
112+
// ProductUtils.copyMetadata(sourceProduct, targetProduct);
113+
114+
if (outputSchillerNNValue) {
115+
final Band nnValueBand = targetProduct.addBand(IdepixConstants.NN_OUTPUT_BAND_NAME, ProductData.TYPE_FLOAT32);
116+
nnValueBand.setNoDataValue(0.0f);
117+
nnValueBand.setNoDataValueUsed(true);
118+
}
119+
}
120+
121+
122+
@Override
123+
public void computeTileStack(Map<Band, Tile> targetTiles, Rectangle rectangle, ProgressMonitor pm) throws OperatorException {
124+
final Tile waterFractionTile = getSourceTile(landWaterBand, rectangle);
125+
126+
final Band olciQualityFlagBand = sourceProduct.getBand(SlstrConstants.OLCI_QUALITY_FLAGS_BAND_NAME);
127+
final Tile olciQualityFlagTile = getSourceTile(olciQualityFlagBand, rectangle);
128+
129+
final Band slstrCloudAnFlagBand = sourceProduct.getBand(SlstrConstants.SLSTR_CLOUD_AN_FLAG_BAND_NAME);
130+
final Tile slstrCloudAnFlagTile = getSourceTile(slstrCloudAnFlagBand, rectangle);
131+
132+
Tile[] olciReflectanceTiles = new Tile[Rad2ReflConstants.OLCI_REFL_BAND_NAMES.length];
133+
float[] olciReflectance = new float[Rad2ReflConstants.OLCI_REFL_BAND_NAMES.length];
134+
for (int i = 0; i < Rad2ReflConstants.OLCI_REFL_BAND_NAMES.length; i++) {
135+
olciReflectanceTiles[i] = getSourceTile(olciReflBands[i], rectangle);
136+
}
137+
138+
final Band cloudFlagTargetBand = targetProduct.getBand(IdepixConstants.CLASSIF_BAND_NAME);
139+
final Tile cloudFlagTargetTile = targetTiles.get(cloudFlagTargetBand);
140+
141+
Band nnTargetBand;
142+
Tile nnTargetTile = null;
143+
if (outputSchillerNNValue) {
144+
nnTargetBand = targetProduct.getBand(IdepixConstants.NN_OUTPUT_BAND_NAME);
145+
nnTargetTile = targetTiles.get(nnTargetBand);
146+
}
147+
try {
148+
for (int y = rectangle.y; y < rectangle.y + rectangle.height; y++) {
149+
checkForCancellation();
150+
for (int x = rectangle.x; x < rectangle.x + rectangle.width; x++) {
151+
initCloudFlag(targetTiles.get(cloudFlagTargetBand), y, x);
152+
final int waterFraction = waterFractionTile.getSampleInt(x, y);
153+
final boolean isL1bLand = olciQualityFlagTile.getSampleBit(x, y, SlstrConstants.L1_F_LAND);
154+
final boolean isLand =
155+
IdepixUtils.isLandPixel(x, y, sourceProduct.getSceneGeoCoding(), isL1bLand, waterFraction);
156+
cloudFlagTargetTile.setSample(x, y, IdepixConstants.IDEPIX_LAND, isLand);
157+
158+
for (int i = 0; i < Rad2ReflConstants.OLCI_REFL_BAND_NAMES.length; i++) {
159+
olciReflectance[i] = olciReflectanceTiles[i].getSampleFloat(x, y);
160+
}
161+
162+
final boolean l1Invalid = olciQualityFlagTile.getSampleBit(x, y, SlstrConstants.L1_F_INVALID);
163+
boolean reflectancesValid = IdepixIO.areAllReflectancesValid(olciReflectance);
164+
cloudFlagTargetTile.setSample(x, y, IdepixConstants.IDEPIX_INVALID, l1Invalid || !reflectancesValid);
165+
166+
final boolean isSlstrCloudAn137Thresh =
167+
slstrCloudAnFlagTile.getSampleBit(x, y, SlstrConstants.CLOUD_AN_F_137_THRESH);
168+
final boolean isSlstrCloudAnGrossCloud =
169+
slstrCloudAnFlagTile.getSampleBit(x, y, SlstrConstants.CLOUD_AN_F_GROSS_CLOUD);
170+
171+
if (reflectancesValid) {
172+
SchillerNeuralNetWrapper nnWrapper = olciSlstrAllNeuralNet.get();
173+
double[] inputVector = nnWrapper.getInputVector();
174+
// use OLCI net instead of OLCI/SLSTR net:
175+
for (int i = 0; i < inputVector.length; i++) {
176+
inputVector[i] = Math.sqrt(olciReflectance[i]);
177+
}
178+
179+
final double nnOutput = nnWrapper.getNeuralNet().calc(inputVector)[0];
180+
181+
if (!cloudFlagTargetTile.getSampleBit(x, y, IdepixConstants.IDEPIX_INVALID)) {
182+
cloudFlagTargetTile.setSample(x, y, IdepixConstants.IDEPIX_CLOUD_AMBIGUOUS, false);
183+
cloudFlagTargetTile.setSample(x, y, IdepixConstants.IDEPIX_CLOUD_SURE, false);
184+
cloudFlagTargetTile.setSample(x, y, IdepixConstants.IDEPIX_CLOUD, false);
185+
cloudFlagTargetTile.setSample(x, y, IdepixConstants.IDEPIX_SNOW_ICE, false);
186+
187+
// CB 20170406: todo: needed here?
188+
final boolean cloudSure = olciReflectance[2] > THRESH_LAND_MINBRIGHT1 &&
189+
nnInterpreter.isCloudSure(nnOutput);
190+
final boolean cloudAmbiguous = olciReflectance[2] > THRESH_LAND_MINBRIGHT2 &&
191+
nnInterpreter.isCloudAmbiguous(nnOutput);
192+
193+
cloudFlagTargetTile.setSample(x, y, IdepixConstants.IDEPIX_CLOUD_AMBIGUOUS, cloudAmbiguous);
194+
cloudFlagTargetTile.setSample(x, y, IdepixConstants.IDEPIX_CLOUD_SURE, cloudSure);
195+
// request RQ, GK, 20220111:
196+
final boolean isSynCloud = cloudAmbiguous || cloudSure || isSlstrCloudAn137Thresh ||
197+
isSlstrCloudAnGrossCloud;
198+
cloudFlagTargetTile.setSample(x, y, IdepixConstants.IDEPIX_CLOUD, cloudAmbiguous || cloudSure);
199+
cloudFlagTargetTile.setSample(x, y, IdepixConstants.IDEPIX_CLOUD, isSynCloud);
200+
cloudFlagTargetTile.setSample(x, y, IdepixConstants.IDEPIX_SNOW_ICE, nnInterpreter.isSnowIce(nnOutput));
201+
}
202+
203+
if (nnTargetTile != null) {
204+
nnTargetTile.setSample(x, y, nnOutput);
205+
}
206+
}
207+
}
208+
}
209+
} catch (Exception e) {
210+
throw new OperatorException("Failed to provide cloud screening:\n" + e.getMessage(), e);
211+
}
212+
}
213+
214+
void initCloudFlag(Tile targetTile, int y, int x) {
215+
targetTile.setSample(x, y, IdepixConstants.IDEPIX_CLOUD, false);
216+
targetTile.setSample(x, y, IdepixConstants.IDEPIX_CLOUD_SURE, false);
217+
targetTile.setSample(x, y, IdepixConstants.IDEPIX_CLOUD_AMBIGUOUS, false);
218+
targetTile.setSample(x, y, IdepixConstants.IDEPIX_SNOW_ICE, false);
219+
targetTile.setSample(x, y, IdepixConstants.IDEPIX_CLOUD_BUFFER, false);
220+
targetTile.setSample(x, y, IdepixConstants.IDEPIX_CLOUD_SHADOW, false);
221+
targetTile.setSample(x, y, IdepixConstants.IDEPIX_COASTLINE, false);
222+
targetTile.setSample(x, y, IdepixConstants.IDEPIX_LAND, true); // already checked
223+
}
224+
225+
public static class Spi extends OperatorSpi {
226+
public Spi() {
227+
super(SlstrClassificationOp.class);
228+
}
229+
}
230+
}

0 commit comments

Comments
 (0)