-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
47 sentinel 1 radiometric calibration (#48)
* Ajusted sorting of file list * Created sub-module for clipping * Created sub-module for clipping * Added calibration of S1 DN values and removing thermal noise * Remove redundant line * Adding xarray to requirements * Updating comments * Fixed linting * Added xmlschema to requirements * Updated docs * Added __init__ file * Extended docstring * Adjusted variable naming * Fixed linting * Updated docstring * Removed debugging code * Added argument denoise * Update docs/dataset.md Co-authored-by: jmaces <[email protected]> * Update eurocropsml/acquisition/clipping/calibration.py Co-authored-by: jmaces <[email protected]> * Update eurocropsml/acquisition/clipping/clipper.py Co-authored-by: jmaces <[email protected]> * Fixed linting * Removing convertion to integer * Changing values to float * Adding None if all clipped values are zero * Changed padding value for filtering None * Adjusted padding value * Adjusted padding value to -999 * Adjusted if-clauses * Updated acquisition pipeline diagram with S1 * Updated diagram * Updated diagram * Updated diagram * patch_median as float * Changed precision of calibration values * Updated colour --------- Co-authored-by: jmaces <[email protected]>
- Loading branch information
Showing
18 changed files
with
442 additions
and
167 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
"""Clipping Sentinel tiles for the EuroCropsML dataset.""" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,170 @@ | ||
"""Utilities for applying radiometric calibration to S-1 data.""" | ||
|
||
from pathlib import Path | ||
from typing import Any, cast | ||
from xml.etree import ElementTree | ||
|
||
import numpy as np | ||
import xarray as xr | ||
import xmlschema | ||
|
||
|
||
def _get_xml_file(filepath: Path, band: str, identifier: str = "calibration") -> Path: | ||
files: list[Path] = list(filepath.iterdir()) | ||
return [file for file in files if f"{band.lower()}" in str(file) and identifier in str(file)][0] | ||
|
||
|
||
def _parse_tag_as_list( | ||
xml_path: Path, | ||
query: str, | ||
schema_dir: Path, | ||
validation: str = "skip", | ||
) -> list[dict[str, Any]]: | ||
"""Function to parse xml tags into list. | ||
Adjusted from xarray-sentinel (https://github.com/bopen/xarray-sentinel). | ||
""" | ||
schema = xmlschema.XMLSchema(schema_dir) | ||
xml_tree = ElementTree.parse(xml_path) | ||
tag: Any = schema.decode(xml_tree, query, validation=validation) | ||
if tag is None: | ||
tag = [] | ||
elif isinstance(tag, dict): | ||
tag = [tag] | ||
tag_list: list[dict[str, Any]] = tag | ||
assert isinstance(tag_list, list), f"{type(tag_list)} is not list" | ||
return tag_list | ||
|
||
|
||
def _open_noise_dataset(safe_dir: Path, band: str) -> xr.Dataset: | ||
"""Function to read noise from LUT. | ||
This reads the NADS (Noise Annotation Data Set) file. | ||
Adjusted from xarray-sentinel (https://github.com/bopen/xarray-sentinel). | ||
""" | ||
xml_dir: Path = safe_dir / "annotation" / "calibration" | ||
schema_dir: Path = safe_dir / "support" / "s1-level-1-noise.xsd" | ||
xml_calibration_file = _get_xml_file(xml_dir, band, identifier="noise") | ||
noise_vectors = _parse_tag_as_list(xml_calibration_file, ".//noiseRangeVector", schema_dir) | ||
|
||
pixel_list = [] | ||
line_list = [] | ||
noise_range_lut_list = [] | ||
for vector in noise_vectors: | ||
line_list.append(vector["line"]) | ||
pixel = np.fromstring(vector["pixel"]["$"], dtype=int, sep=" ") | ||
pixel_list.append(pixel) | ||
noise_range_lut = np.fromstring(vector["noiseRangeLut"]["$"], dtype=np.float32, sep=" ") | ||
noise_range_lut_list.append(noise_range_lut) | ||
|
||
pixel = np.array(pixel_list) | ||
if (pixel - pixel[0]).any(): | ||
raise ValueError("Unable to organize noise vectors in a regular line-pixel grid") | ||
data_vars = { | ||
"noiseRangeLut": (("line", "pixel"), noise_range_lut_list), | ||
} | ||
coords = {"line": line_list, "pixel": pixel_list[0]} | ||
|
||
return xr.Dataset(data_vars=data_vars, coords=coords) | ||
|
||
|
||
def _open_calibration_dataset(safe_dir: Path, band: str) -> xr.Dataset: | ||
"""Function to read calibration from LUT. | ||
This reads the CADS (Calibration Annotation Data Set) file. | ||
Adjusted from xarray-sentinel (https://github.com/bopen/xarray-sentinel). | ||
""" | ||
xml_dir: Path = safe_dir / "annotation" / "calibration" | ||
schema_dir: Path = safe_dir / "support" / "s1-level-1-calibration.xsd" | ||
xml_calibration_file = _get_xml_file(xml_dir, band) | ||
calibration_vectors = _parse_tag_as_list( | ||
xml_calibration_file, ".//calibrationVector", schema_dir | ||
) | ||
|
||
pixel_list = [] | ||
line_list = [] | ||
sigmanought_list = [] | ||
for vector in calibration_vectors: | ||
line_list.append(vector["line"]) | ||
pixel = np.fromstring(vector["pixel"]["$"], dtype=int, sep=" ") | ||
pixel_list.append(pixel) | ||
sigma_nought = np.fromstring(vector["sigmaNought"]["$"], dtype=np.float32, sep=" ") | ||
sigmanought_list.append(sigma_nought) | ||
|
||
pixel = np.array(pixel_list) | ||
if (pixel - pixel[0]).any(): | ||
raise ValueError("Unable to organise calibration vectors in a regular line-pixel grid") | ||
data_vars = { | ||
"sigmaNought": (("line", "pixel"), sigmanought_list), | ||
} | ||
coords = {"line": line_list, "pixel": pixel_list[0]} | ||
|
||
return xr.Dataset(data_vars=data_vars, coords=coords) | ||
|
||
|
||
def _get_lut_value( | ||
digital_number: xr.DataArray, available_lut: xr.DataArray, **kwargs: Any | ||
) -> xr.DataArray: | ||
lut_mean = available_lut.mean() | ||
if np.allclose(lut_mean, available_lut, **kwargs): | ||
lut: xr.DataArray = lut_mean.astype(np.float64) | ||
else: | ||
lut = available_lut.interp( | ||
line=digital_number.line, | ||
pixel=digital_number.pixel, | ||
).astype(np.float64) | ||
if digital_number.chunks is not None: | ||
lut = lut.chunk(digital_number.chunksizes) | ||
|
||
return lut | ||
|
||
|
||
def _calibrate( | ||
digital_number: xr.DataArray, | ||
backscatter_calibration_lut: xr.DataArray, | ||
thermal_noise_lut: xr.DataArray | None = None, | ||
**kwargs: Any, | ||
) -> xr.DataArray: | ||
"""Return the calibrated sigma nought (backscatter coefficient) using the calibration LUT. | ||
Apply thermal noise removal if wanted. | ||
Adjusted from xarray-sentinel (https://github.com/bopen/xarray-sentinel). | ||
digital_number: Digital numbers from the original raster tile to be calibrated. | ||
backscatter_calibration_lut: Calibration LUT for backscatter (sigma nought). | ||
thermal_noise_lut: Thermal noise LUT to remove sensor noise. | ||
""" | ||
radar_intensity = digital_number**2 | ||
backscatter_calibration = _get_lut_value(digital_number, backscatter_calibration_lut, **kwargs) | ||
if thermal_noise_lut is not None: | ||
thermal_noise = _get_lut_value(digital_number, thermal_noise_lut, **kwargs) | ||
radar_intensity = radar_intensity - thermal_noise | ||
sigma_nought: xr.DataArray = radar_intensity / backscatter_calibration**2 | ||
return abs(sigma_nought) | ||
|
||
|
||
def _calibrate_digital_number_in_db( | ||
digital_number: xr.DataArray, | ||
backscatter_calibration_lut: xr.DataArray, | ||
thermal_noise_lut: xr.DataArray | None = None, | ||
min_db: float | None = -50.0, | ||
) -> np.ndarray: | ||
"""Return calibrated sigma nought (backscatter coefficient) in dB using the calibration LUT. | ||
Adjusted from xarray-sentinel (https://github.com/bopen/xarray-sentinel). | ||
digital_number: Digital numbers from the original raster tile to be calibrated. | ||
backscatter_calibration_lut: calibration LUT (sigmaNought). | ||
thermal_noise_lut: Thermal noise LUT. | ||
min_db: minimal value in db, to avoid infinity values. | ||
""" | ||
sigma_nought = _calibrate(digital_number, backscatter_calibration_lut, thermal_noise_lut) | ||
# convert to decibels (dB) | ||
sigma_nought_db = 10.0 * np.log10(np.maximum(sigma_nought, 1e-10)) # prevent division by 0 | ||
|
||
if min_db is not None: | ||
sigma_nought_db = cast(xr.DataArray, np.maximum(sigma_nought_db, min_db)) | ||
|
||
return cast(np.ndarray, sigma_nought_db.values) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.