# SNAP processing

s1bursts relies on ESA SNAP for the SAR and InSAR processing. This page describes the steps SNAP applies to a burst, how to inspect them for your own settings, which SNAP problems s1bursts corrects, and how the supported SNAP versions differ.


# The processing steps

Each product runs through a fixed chain of SNAP operators, called a processing graph. s1bursts builds the graph from the product's settings object, so the settings decide which steps run and with which parameters. The RTC backscatter graph has eight steps with the default settings:

| Step | What it does | Settings |
|----|----|----|
| `Apply-Orbit-File` | Replaces the predicted satellite orbit with the precise one |  |
| `ThermalNoiseRemoval` | Removes the instrument's own noise |  |
| `Calibration` | Converts the raw values to beta-0 radar brightness |  |
| `TOPSAR-Deburst` | Keeps only the burst's valid lines and samples, dropping the empty margin around it |  |
| `Multilook` | Averages neighboring pixels to reduce noise and runtime | `multilook` (`None` skips it) |
| `Terrain-Flattening` | Corrects brightness for the slope of the terrain (RTC only) | `dem_oversampling`, `additional_overlap`, DEM settings |
| `ReplaceZerosWithNoData` | Marks the empty border of the burst as missing ([why](#corrections-s1bursts-applies)) | `replace_zeros_with_nan` (`False` skips it) |
| `Terrain-Correction` | Places every pixel on the map, using the DEM | `grid_spacing`, `img_resampling`, `align_to_standard_grid`, DEM settings |

The DEM settings are `dem`, `dem_resampling` and, for an [external DEM](external-dems.md), `external_dem_nodata` and `external_dem_apply_egm`.

The GTC graph of [GTCConfig](../reference/snap_graph.GTCConfig.md#s1bursts.snap_graph.GTCConfig) is the same without `Terrain-Flattening`. Coherence compares two acquisitions, so its graph differs. Both acquisitions get precise orbits, then `Back-Geocoding` aligns the second onto the first pixel by pixel, using the DEM. `Coherence` estimates the similarity of the two within a moving window, set by `window`. `TOPSAR-Deburst`, `ReplaceZerosWithNoData` and `Terrain-Correction` then run as in the backscatter graph. The coherence graph has no noise removal, calibration or multilooking.


# Inspect a graph

Every settings object can show the graph it produces, as a diagram or as the XML file that SNAP runs, to check what your settings change.


## As a diagram

[to_mermaid()](../reference/snap_graph.GeocodingConfig.md#s1bursts.snap_graph.GeocodingConfig.to_mermaid) returns the graph as text in the [Mermaid](https://mermaid.js.org/) diagram language. In a notebook, the [mermaid-py](https://github.com/ouhammmourachid/mermaid-py) package draws it. Install it separately, since s1bursts does not depend on it:

``` python
from mermaid import Mermaid
from s1bursts import RTCConfig

config = RTCConfig(grid_spacing=20)
Mermaid(config.to_mermaid())
```

mermaid-py draws the diagram through the online mermaid.ink service. Without internet access, paste the text into a `mermaid` code block on GitHub or in Quarto instead. For `RTCConfig(grid_spacing=20)`, the graph looks like this:


*\[Rich HTML output -- view on the documentation site\]*


## As XML

[to_xml()](../reference/snap_graph.CoherenceConfig.md#s1bursts.snap_graph.CoherenceConfig.to_xml) returns the graph in the XML format of SNAP's Graph Processing Tool (GPT), with every parameter SNAP receives:


``` python
xml = config.to_xml()
```


> **Note: The full graph**
>
> ``` xml
> <graph id="Graph">
>   <version>1.0</version>
>   <node id="Apply-Orbit-File">
>     <operator>Apply-Orbit-File</operator>
>     <sources>
>       <sourceProduct>${source}</sourceProduct>
>     </sources>
>     <parameters class="com.bc.ceres.binding.dom.XppDomElement">
>       <orbitType>Sentinel Precise (Auto Download)</orbitType>
>       <polyDegree>3</polyDegree>
>       <continueOnFail>false</continueOnFail>
>     </parameters>
>   </node>
>   <node id="ThermalNoiseRemoval">
>     <operator>ThermalNoiseRemoval</operator>
>     <sources>
>       <sourceProduct refid="Apply-Orbit-File" />
>     </sources>
>     <parameters class="com.bc.ceres.binding.dom.XppDomElement">
>       <selectedPolarisations />
>       <removeThermalNoise>true</removeThermalNoise>
>       <outputNoise>false</outputNoise>
>       <reIntroduceThermalNoise>false</reIntroduceThermalNoise>
>     </parameters>
>   </node>
>   <node id="Calibration">
>     <operator>Calibration</operator>
>     <sources>
>       <sourceProduct refid="ThermalNoiseRemoval" />
>     </sources>
>     <parameters class="com.bc.ceres.binding.dom.XppDomElement">
>       <sourceBands />
>       <auxFile>Latest Auxiliary File</auxFile>
>       <externalAuxFile />
>       <outputImageInComplex>false</outputImageInComplex>
>       <outputImageScaleInDb>false</outputImageScaleInDb>
>       <createGammaBand>false</createGammaBand>
>       <createBetaBand>false</createBetaBand>
>       <selectedPolarisations />
>       <outputSigmaBand>false</outputSigmaBand>
>       <outputGammaBand>false</outputGammaBand>
>       <outputBetaBand>true</outputBetaBand>
>     </parameters>
>   </node>
>   <node id="TOPSAR-Deburst">
>     <operator>TOPSAR-Deburst</operator>
>     <sources>
>       <sourceProduct refid="Calibration" />
>     </sources>
>     <parameters class="com.bc.ceres.binding.dom.XppDomElement">
>       <selectedPolarisations />
>     </parameters>
>   </node>
>   <node id="Multilook">
>     <operator>Multilook</operator>
>     <sources>
>       <sourceProduct refid="TOPSAR-Deburst" />
>     </sources>
>     <parameters class="com.bc.ceres.binding.dom.XppDomElement">
>       <sourceBands />
>       <nRgLooks>5</nRgLooks>
>       <nAzLooks>1</nAzLooks>
>       <outputIntensity>true</outputIntensity>
>       <grSquarePixel>false</grSquarePixel>
>     </parameters>
>   </node>
>   <node id="Terrain-Flattening">
>     <operator>Terrain-Flattening</operator>
>     <sources>
>       <sourceProduct refid="Multilook" />
>     </sources>
>     <parameters class="com.bc.ceres.binding.dom.XppDomElement">
>       <sourceBands />
>       <demName>Copernicus 30m Global DEM</demName>
>       <demResamplingMethod>BILINEAR_INTERPOLATION</demResamplingMethod>
>       <externalDEMFile />
>       <externalDEMNoDataValue>0.0</externalDEMNoDataValue>
>       <externalDEMApplyEGM>false</externalDEMApplyEGM>
>       <outputSimulatedImage>true</outputSimulatedImage>
>       <outputSigma0>false</outputSigma0>
>       <nodataValueAtSea>true</nodataValueAtSea>
>       <additionalOverlap>0.2</additionalOverlap>
>       <oversamplingMultiple>1.0</oversamplingMultiple>
>     </parameters>
>   </node>
>   <node id="ReplaceZerosWithNoData">
>     <operator>BandMaths</operator>
>     <sources>
>       <sourceProduct refid="Terrain-Flattening" />
>     </sources>
>     <parameters class="com.bc.ceres.binding.dom.XppDomElement">
>       <targetBands>
>         <targetBand>
>           <name>gamma0</name>
>           <type>float32</type>
>           <expression>Gamma0_${swath}_${polarization} > 0 ? Gamma0_${swath}_${polarization} : -1099511627776.0</expression>
>           <noDataValue>-1099511627776.0</noDataValue>
>         </targetBand>
>         <targetBand>
>           <name>scattering_area</name>
>           <type>float32</type>
>           <expression>simulatedImage > 0 ? simulatedImage : -1099511627776.0</expression>
>           <noDataValue>-1099511627776.0</noDataValue>
>         </targetBand>
>       </targetBands>
>       <variables />
>     </parameters>
>   </node>
>   <node id="Terrain-Correction">
>     <operator>Terrain-Correction</operator>
>     <sources>
>       <sourceProduct refid="ReplaceZerosWithNoData" />
>     </sources>
>     <parameters class="com.bc.ceres.binding.dom.XppDomElement">
>       <sourceBands />
>       <demName>Copernicus 30m Global DEM</demName>
>       <externalDEMFile />
>       <externalDEMNoDataValue>0.0</externalDEMNoDataValue>
>       <externalDEMApplyEGM>false</externalDEMApplyEGM>
>       <demResamplingMethod>BILINEAR_INTERPOLATION</demResamplingMethod>
>       <imgResamplingMethod>BILINEAR_INTERPOLATION</imgResamplingMethod>
>       <pixelSpacingInMeter>20.0</pixelSpacingInMeter>
>       <mapProjection>${crs}</mapProjection>
>       <alignToStandardGrid>true</alignToStandardGrid>
>       <standardGridOriginX>10.0</standardGridOriginX>
>       <standardGridOriginY>10.0</standardGridOriginY>
>       <nodataValueAtSea>true</nodataValueAtSea>
>       <saveDEM>false</saveDEM>
>       <saveLatLon>false</saveLatLon>
>       <saveIncidenceAngleFromEllipsoid>false</saveIncidenceAngleFromEllipsoid>
>       <saveLocalIncidenceAngle>false</saveLocalIncidenceAngle>
>       <saveProjectedLocalIncidenceAngle>false</saveProjectedLocalIncidenceAngle>
>       <saveSelectedSourceBand>true</saveSelectedSourceBand>
>       <saveLayoverShadowMask>false</saveLayoverShadowMask>
>       <outputComplex>false</outputComplex>
>       <applyRadiometricNormalization>false</applyRadiometricNormalization>
>       <saveSigmaNought>false</saveSigmaNought>
>       <saveGammaNought>false</saveGammaNought>
>       <saveBetaNought>false</saveBetaNought>
>       <incidenceAngleForSigma0>Use projected local incidence angle from DEM</incidenceAngleForSigma0>
>       <incidenceAngleForGamma0>Use projected local incidence angle from DEM</incidenceAngleForGamma0>
>       <auxFile>Latest Auxiliary File</auxFile>
>       <externalAuxFile />
>     </parameters>
>   </node>
> </graph>
> ```

The graph leaves two placeholders, `${source}` for the burst file and `${crs}` for the UTM zone. s1bursts fills them in for every burst it processes, which is why the UTM zone is passed to the `create_*` methods rather than stored in the settings. [CoherenceConfig](../reference/snap_graph.CoherenceConfig.md#s1bursts.snap_graph.CoherenceConfig) returns its graph the same way, with `${primary}` and `${secondary}` in place of `${source}`.


## For your SNAP version

The coherence graph differs slightly between SNAP 13 and 14. Without a version, s1bursts builds the graph for SNAP 13. Pass the version of your installation to see the exact graph that processing runs:

``` python
from s1bursts import CoherenceConfig, SNAPConfig

version = SNAPConfig().resolved_snap_version
xml = CoherenceConfig().to_xml(snap_version=version)
```


# Corrections s1bursts applies

s1bursts works around four SNAP problems, so you do not need to:

- **External DEMs are read half a pixel off** ([forum report](https://forum.step.esa.int/t/external-dem-is-sampled-half-a-pixel-off/46042)). [prepare_dem_for_snap()](../reference/dem.prepare_dem_for_snap.md#s1bursts.dem.prepare_dem_for_snap) shifts the DEM to compensate, but only before the 14.0.2 update of SNAP's DEM module, which fixed it.
- **DEM heights use the wrong sea-level model** ([forum report](https://forum.step.esa.int/t/copernicus-dem-built-in-path-applies-egm96-while-the-data-is-egm2008/46041)). SNAP knows only the EGM96 model, even for DEMs based on another. [prepare_dem_for_snap()](../reference/dem.prepare_dem_for_snap.md#s1bursts.dem.prepare_dem_for_snap) converts external DEM heights itself (see [Using an external DEM](external-dems.md)). The built-in Copernicus DEM is affected too. To avoid it, download the Copernicus DEM and prepare it as an external DEM with `source_crs="EPSG:4326+3855"`.
- **Burst edges come out too dark** ([forum report](https://forum.step.esa.int/t/sentinel-1-slc-terrain-correction-operator-uses-zero-values-in-interpolation-instead-of-treating-them-as-no-data/45336)). SNAP mixes the empty border of the input into the edge pixels during terrain correction, which leaves dark seams along the burst edges. s1bursts marks that border as missing before terrain correction.
- **Areas without DEM coverage become zeros.** Where the DEM has no data, for example over the sea at the edge of a burst, SNAP can write zeros instead of missing values. s1bursts turns these into `NaN`.


# SNAP versions

s1bursts works with SNAP 13 and 14. It reads the installed version and adapts the graphs to it, so the same code runs on either. The version is recorded in every product's `PROCESSING_SOFTWARE_VERSION` tag. An existing product of another version is not skipped, and products of two versions are not combined into one composite.

> **Note: Keep one SNAP version per study**
>
> SNAP versions give slightly different results for the same burst, so a time series that switches version contains an artificial step. Between SNAP 13 and 14, backscatter differs by a median of about 0.05 dB and coherence by about 0.004 on average.

SNAP 14 also adds two coherence options, exposed as `CoherenceConfig.correct_coherence_bias` and `remove_local_phase_ramp`. Both are off by default and need SNAP 14 or newer. Products record them in the `COHERENCE_BIAS_CORRECTED` and `LOCAL_PHASE_RAMP_REMOVED` tags: `false` under SNAP 13, and `N/A` when the SNAP version could not be determined, since SNAP 14 then applies its own default, which corrects the bias.


# Validation

s1bursts products are checked against products of the same bursts from independent processors: backscatter against [OPERA RTC-S1](https://hyp3-docs.asf.alaska.edu/guides/opera_rtc_product_guide/) from NASA/JPL, and coherence against [ASF HyP3](https://hyp3-docs.asf.alaska.edu/guides/burst_insar_product_guide) burst InSAR products. Both use the same Copernicus DEM, so the comparison tests the processing itself. The comparisons are tests in the repository, [`test_opera_validation.py`](https://github.com/lqgentner/s1bursts/blob/main/tests/test_opera_validation.py) and [`test_insar_validation.py`](https://github.com/lqgentner/s1bursts/blob/main/tests/test_insar_validation.py). [`REFERENCE.md`](https://github.com/lqgentner/s1bursts/blob/main/tests/fixtures/REFERENCE.md) describes the reference products and what each test checks.
