[Git][debian-gis-team/xradarsat2][upstream] New upstream version 2025.10.15+ds
Antonio Valentino (@antonio.valentino)
gitlab at salsa.debian.org
Thu Oct 23 07:14:59 BST 2025
Antonio Valentino pushed to branch upstream at Debian GIS Project / xradarsat2
Commits:
9c5bd253 by Antonio Valentino at 2025-10-23T06:11:30+00:00
New upstream version 2025.10.15+ds
- - - - -
18 changed files:
- .git_archival.txt
- + .github/workflows/ci.yaml
- .github/workflows/publish.yml
- + .github/workflows/upstream-dev.yaml
- .gitignore
- + ci/install-upstream-dev.sh
- + ci/requirements/docs.yaml
- + ci/requirements/environment.yaml
- docs/doc_xradarsat2.ipynb
- + end2end/check_opening_datatree_radarsat2.py
- pyproject.toml
- src/xradarsat2/__init__.py
- src/xradarsat2/radarSat2_tiff_reader.py
- src/xradarsat2/radarSat2_xarray_reader.py
- + src/xradarsat2/tests/test_radarSat2_xarray_reader.py
- src/xradarsat2/utils.py
- src/xradarsat2/xradarsat2-config.yaml
- test/test_opening_datatree_radarsat2.py
Changes:
=====================================
.git_archival.txt
=====================================
@@ -1,4 +1,4 @@
-node: de1141391ab0b197ade12e6427804c6e89b95d80
-node-date: 2024-11-12T14:26:55+01:00
-describe-name: 2024.11.12
-ref-names: HEAD -> main, tag: 2024.11.12
+node: 4391409af1c60cb29670a1c3ff849b40dd88fe0b
+node-date: 2025-10-15T12:08:45+02:00
+describe-name: 2025.10.15
+ref-names: tag: 2025.10.15
=====================================
.github/workflows/ci.yaml
=====================================
@@ -0,0 +1,85 @@
+name: CI
+
+on:
+ push:
+ branches: [main]
+ pull_request:
+ branches: [main]
+ workflow_dispatch:
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+
+jobs:
+ detect-skip-ci-trigger:
+ name: "Detect CI Trigger: [skip-ci]"
+ if: |
+ github.repository == 'umr-lops/xradarsat2'
+ && (
+ github.event_name == 'push' || github.event_name == 'pull_request'
+ )
+ runs-on: ubuntu-latest
+ outputs:
+ triggered: ${{ steps.detect-trigger.outputs.trigger-found }}
+ steps:
+ - uses: actions/checkout at v4
+ with:
+ fetch-depth: 2
+ - uses: xarray-contrib/ci-trigger at v1
+ id: detect-trigger
+ with:
+ keyword: "[skip-ci]"
+
+ ci:
+ name: ${{ matrix.os }} py${{ matrix.python-version }}
+ runs-on: ${{ matrix.os }}
+ needs: detect-skip-ci-trigger
+
+ if: needs.detect-skip-ci-trigger.outputs.triggered == 'false'
+
+ defaults:
+ run:
+ shell: bash -l {0}
+
+ strategy:
+ fail-fast: false
+ matrix:
+ python-version: ["3.10", "3.11", "3.12"]
+ os: ["ubuntu-latest", "macos-latest", "windows-latest"]
+
+ steps:
+ - name: Checkout the repository
+ uses: actions/checkout at v4
+ with:
+ # need to fetch all tags to get a correct version
+ fetch-depth: 0 # fetch all branches and tags
+
+ - name: Setup environment variables
+ run: |
+ echo "TODAY=$(date +'%Y-%m-%d')" >> $GITHUB_ENV
+
+ echo "CONDA_ENV_FILE=ci/requirements/environment.yaml" >> $GITHUB_ENV
+
+ - name: Setup micromamba
+ uses: mamba-org/setup-micromamba at v2
+ with:
+ environment-file: ${{ env.CONDA_ENV_FILE }}
+ environment-name: xradarsat2-tests
+ cache-environment: true
+ generate-run-shell: true
+ cache-environment-key: "${{runner.os}}-${{runner.arch}}-py${{matrix.python-version}}-${{env.TODAY}}-${{hashFiles(env.CONDA_ENV_FILE)}}"
+ create-args: >-
+ python=${{matrix.python-version}}
+
+ - name: Install xradarsat2
+ run: |
+ python -m pip install --no-deps -e .
+
+ - name: Import xradarsat2
+ run: |
+ python -c "import xradarsat2"
+
+ - name: Run tests
+ run: |
+ python -m pytest --cov=xradarsat2
=====================================
.github/workflows/publish.yml
=====================================
@@ -12,9 +12,9 @@ jobs:
contents: 'read'
id-token: 'write'
steps:
- - uses: actions/checkout at v4
+ - uses: actions/checkout at v5
- name: Set up Python
- uses: actions/setup-python at v5
+ uses: actions/setup-python at v6
with:
python-version: '3.x'
- name: Install dependencies
@@ -28,7 +28,7 @@ jobs:
twine check dist/*
pip install dist/*.whl
- name: Publish to PyPI
- uses: pypa/gh-action-pypi-publish at 15c56dba361d8335944d31a2ecd17d700fc7bcbc
+ uses: pypa/gh-action-pypi-publish at ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e
with:
password: ${{ secrets.pypi_token }}
repository_url: https://upload.pypi.org/legacy/
=====================================
.github/workflows/upstream-dev.yaml
=====================================
@@ -0,0 +1,98 @@
+name: upstream-dev CI
+
+on:
+ push:
+ branches: [main]
+ pull_request:
+ branches: [main]
+ schedule:
+ - cron: "0 18 * * 0" # Weekly "On Sundays at 18:00" UTC
+ workflow_dispatch:
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+
+jobs:
+ detect-test-upstream-trigger:
+ name: "Detect CI Trigger: [test-upstream]"
+ if: github.event_name == 'push' || github.event_name == 'pull_request'
+ runs-on: ubuntu-latest
+ outputs:
+ triggered: ${{ steps.detect-trigger.outputs.trigger-found }}
+ steps:
+ - uses: actions/checkout at v4
+ with:
+ fetch-depth: 2
+ - uses: xarray-contrib/ci-trigger at v1.2
+ id: detect-trigger
+ with:
+ keyword: "[test-upstream]"
+
+ upstream-dev:
+ name: upstream-dev
+ runs-on: ubuntu-latest
+ needs: detect-test-upstream-trigger
+
+ if: |
+ always()
+ && github.repository == 'umr-lops/xradarsat2'
+ && (
+ github.event_name == 'schedule'
+ || github.event_name == 'workflow_dispatch'
+ || needs.detect-test-upstream-trigger.outputs.triggered == 'true'
+ || contains(github.event.pull_request.labels.*.name, 'run-upstream')
+ )
+
+ defaults:
+ run:
+ shell: bash -l {0}
+
+ strategy:
+ fail-fast: false
+ matrix:
+ python-version: ["3.12"]
+
+ steps:
+ - name: checkout the repository
+ uses: actions/checkout at v4
+ with:
+ # need to fetch all tags to get a correct version
+ fetch-depth: 0 # fetch all branches and tags
+
+ - name: set up conda environment
+ uses: mamba-org/setup-micromamba at v1
+ with:
+ environment-file: ci/requirements/environment.yaml
+ environment-name: tests
+ create-args: >-
+ python=${{ matrix.python-version }}
+ pytest-reportlog
+
+ - name: install upstream-dev dependencies
+ run: bash ci/install-upstream-dev.sh
+
+ - name: install the package
+ run: python -m pip install --no-deps -e .
+
+ - name: show versions
+ run: python -m pip list
+
+ - name: import
+ run: |
+ python -c 'import xradarsat2'
+
+ - name: run tests
+ if: success()
+ id: status
+ run: |
+ python -m pytest -rf --report-log=pytest-log.jsonl
+
+ - name: report failures
+ if: |
+ failure()
+ && steps.tests.outcome == 'failure'
+ && github.event_name == 'schedule'
+ uses: xarray-contrib/issue-from-pytest-log at v1
+ with:
+ log-path: pytest-log.jsonl
=====================================
.gitignore
=====================================
@@ -129,4 +129,4 @@ tmp/
/.idea/
dask-worker-space/
-localxradarsat2-config.yaml
\ No newline at end of file
+localxradarsat2-config.yaml
=====================================
ci/install-upstream-dev.sh
=====================================
@@ -0,0 +1,14 @@
+#!/usr/bin/env bash
+
+conda remove -y --force cytoolz numpy xarray toolz python-dateutil rioxarray
+python -m pip install \
+ -i https://pypi.anaconda.org/scientific-python-nightly-wheels/simple \
+ --no-deps \
+ --pre \
+ --upgrade \
+ numpy \
+ rioxarray \
+ xarray
+python -m pip install --upgrade \
+ git+https://github.com/pytoolz/toolz \
+ git+https://github.com/dateutil/dateutil
=====================================
ci/requirements/docs.yaml
=====================================
@@ -0,0 +1,8 @@
+name: xradarsat2-docs
+channels:
+ - conda-forge
+dependencies:
+ - python=3.11
+ - sphinx>=4
+ - sphinx_book_theme
+ - ipython
=====================================
ci/requirements/environment.yaml
=====================================
@@ -0,0 +1,26 @@
+name: xradarsat2-tests
+channels:
+ - conda-forge
+dependencies:
+ - python
+ - ipython
+ - pre-commit
+ - pytest
+ - pytest-reportlog
+ - pytest-cov
+ - numpy
+ - toolz
+ - cytoolz
+ - python-dateutil
+ - construct
+ - xarray
+ - rioxarray>=0.17.0
+ - aiohttp
+ - fsspec
+ - dask
+ - opencv
+ - pyyaml
+ - affine
+ - h5netcdf
+ - scipy
+ - xmltodict
=====================================
docs/doc_xradarsat2.ipynb
=====================================
@@ -2,7 +2,7 @@
"cells": [
{
"cell_type": "markdown",
- "id": "03cfc89b-5ee1-49c2-8bc9-ca88c00eb63b",
+ "id": "0",
"metadata": {},
"source": [
"# examples"
@@ -11,7 +11,7 @@
{
"cell_type": "code",
"execution_count": null,
- "id": "9f901d61-adf5-4ad2-a721-b92fb8555642",
+ "id": "1",
"metadata": {},
"outputs": [],
"source": [
@@ -23,7 +23,7 @@
{
"cell_type": "code",
"execution_count": null,
- "id": "84c1a31d-c27c-4614-8a39-f4dca2801bf6",
+ "id": "2",
"metadata": {},
"outputs": [],
"source": [
@@ -36,7 +36,7 @@
{
"cell_type": "code",
"execution_count": null,
- "id": "90fb68ff-2c19-4432-8d95-98159ee546a8",
+ "id": "3",
"metadata": {},
"outputs": [],
"source": [
@@ -48,7 +48,7 @@
{
"cell_type": "code",
"execution_count": null,
- "id": "ff1730a0-caeb-4c53-8fa0-85c79adfb5a6",
+ "id": "4",
"metadata": {},
"outputs": [],
"source": [
@@ -59,7 +59,7 @@
{
"cell_type": "code",
"execution_count": null,
- "id": "6f74bb35-b8d0-4a17-82d2-4b151e3044fb",
+ "id": "5",
"metadata": {},
"outputs": [],
"source": [
@@ -74,11 +74,6 @@
}
],
"metadata": {
- "kernelspec": {
- "display_name": "Python 3 (ipykernel)",
- "language": "python",
- "name": "python3"
- },
"language_info": {
"codemirror_mode": {
"name": "ipython",
@@ -88,8 +83,7 @@
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
- "pygments_lexer": "ipython3",
- "version": "3.11.0"
+ "pygments_lexer": "ipython3"
}
},
"nbformat": 4,
=====================================
end2end/check_opening_datatree_radarsat2.py
=====================================
@@ -0,0 +1,25 @@
+from xradarsat2.utils import load_config
+import xradarsat2
+import time
+import logging
+
+logging.basicConfig(level=logging.DEBUG)
+logging.debug("start opening RadarSAT-2 product")
+# conf = getconfig.get_config()
+# subswath = conf['product_paths'][0]
+
+t0 = time.time()
+conf = load_config()
+folder_path = conf["folder_path"]
+dt = xradarsat2.rs2_reader(folder_path)
+elapse_t = time.time() - t0
+
+print(type(dt), dt)
+print("out of the reader")
+print(dt)
+print("time to read the SAFE through nfs: %1.2f sec" % elapse_t)
+dt = xradarsat2.load_digital_number(
+ dt, chunks={"pol": "VV", "line": 6000, "sample": 8000}
+)
+print("DN", dt["/digital_numbers"])
+# pdb.set_trace()
=====================================
pyproject.toml
=====================================
@@ -15,31 +15,65 @@ fallback_version = "9999"
[project]
name = "xradarsat2"
-authors = [
- { name="Yann Reynaud", email="Yann.Reynaud.2 at ifremer.fr" },
-]
-license = {text = "MIT"}
-description = "xarray/dask distributed L1 sar file reader for radarSat2"
+authors = [{ name = "Yann Reynaud", email = "Yann.Reynaud.2 at ifremer.fr" }]
+license = { text = "MIT" }
+description = "xarray Level-1 SAR file reader for radarSat2"
readme = "README.md"
requires-python = ">=3.9"
classifiers = [
- "Programming Language :: Python :: 3",
- "License :: OSI Approved :: MIT License",
- "Operating System :: OS Independent",
+ "Programming Language :: Python :: 3",
+ "License :: OSI Approved :: MIT License",
+ "Operating System :: OS Independent",
]
dependencies = [
- 'xmltodict',
- 'numpy',
- "xarray>=2024.10.0",
- 'rasterio',
- 'rioxarray',
- 'dask',
- 'affine',
- 'regex',
- 'pyyaml'
+ 'xmltodict',
+ 'numpy',
+ "xarray>=2024.10.0",
+ 'rasterio',
+ 'rioxarray>=0.18.1',
+ 'dask',
+ 'affine',
+ 'regex',
+ 'pyyaml',
+ 'fsspec',
+ 'aiohttp',
+
]
dynamic = ["version"]
[project.urls]
"Homepage" = "https://github.com/umr-lops/xradarsat2"
"Bug Tracker" = "https://github.com/umr-lops/xradarsat2/issues"
+
+[tool.coverage.report]
+show_missing = true
+exclude_lines = ["pragma: no cover", "if TYPE_CHECKING"]
+
+[tool.ruff.lint]
+ignore = [
+ "E402", # module level import not at top of file
+ "E501", # line too long - let black worry about that
+ "E731", # do not assign a lambda expression, use a def
+ "UP038", # type union instead of tuple for isinstance etc
+]
+select = [
+ "F", # Pyflakes
+ "E", # Pycodestyle
+ "I", # isort
+ "UP", # Pyupgrade
+ "TID", # flake8-tidy-imports
+ "W",
+]
+extend-safe-fixes = [
+ "TID252", # absolute imports
+ "UP031", # percent string interpolation
+]
+fixable = ["I", "TID252", "UP"]
+
+[tool.ruff.lint.isort]
+known-first-party = ["xradarsat2"]
+known-third-party = ["xarray", "toolz", "construct"]
+
+[tool.ruff.lint.flake8-tidy-imports]
+# Disallow all relative imports.
+ban-relative-imports = "all"
=====================================
src/xradarsat2/__init__.py
=====================================
@@ -1,7 +1,10 @@
-from xradarsat2.radarSat2_xarray_reader import load_digital_number # noqa: F401
-from xradarsat2.radarSat2_xarray_reader import rs2_reader # noqa: F401
from importlib.metadata import version
+from xradarsat2.radarSat2_xarray_reader import (
+ load_digital_number, # noqa: F401
+ rs2_reader, # noqa: F401
+)
+
try:
__version__ = version("xradarsat2")
except Exception:
=====================================
src/xradarsat2/radarSat2_tiff_reader.py
=====================================
@@ -8,6 +8,7 @@ import rioxarray
import xarray as xr
import yaml
from affine import Affine
+
from xradarsat2.utils import get_glob, load_config
# folder_path = "/home/datawork-cersat-public/cache/project/sarwing/data/RS2/L1/VV/2010/288/" \
@@ -43,12 +44,7 @@ def _load_digital_number(
tiff_files = list_tiff_files(root_path)
map_dims = {"pol": "band", "line": "y", "sample": "x"}
if resolution is not None:
- comment = 'resampled at "%s" with %s.%s.%s' % (
- resolution,
- resampling.__module__,
- resampling.__class__.__name__,
- resampling.name,
- )
+ comment = f'resampled at "{resolution}" with {resampling.__module__}.{resampling.__class__.__name__}.{resampling.name}'
else:
comment = "read at full resolution"
@@ -158,7 +154,7 @@ def _load_digital_number(
var_name = "digital_number"
dn.attrs = {
- "comment": "%s digital number, %s" % (descr, comment),
+ "comment": f"{descr} digital number, {comment}",
"history": yaml.safe_dump(
{var_name: get_glob([p.replace(root_path + "/", "") for p in tiff_files])}
),
=====================================
src/xradarsat2/radarSat2_xarray_reader.py
=====================================
@@ -4,6 +4,7 @@ import glob
import os
import re
import traceback
+from datetime import datetime
import dask
import numpy as np
@@ -13,7 +14,6 @@ import xarray as xr
import xmltodict
import yaml
from affine import Affine
-from datetime import datetime
xpath_dict = {
"geolocation_grid": {
@@ -1896,12 +1896,7 @@ def load_digital_number(
tiff_files, pols = sort_list_files_and_get_pols(tiff_files)
map_dims = {"pol": "band", "line": "y", "sample": "x"}
if resolution is not None:
- comment = 'resampled at "%s" with %s.%s.%s' % (
- resolution,
- resampling.__module__,
- resampling.__class__.__name__,
- resampling.name,
- )
+ comment = f'resampled at "{resolution}" with {resampling.__module__}.{resampling.__class__.__name__}.{resampling.name}'
else:
comment = "read at full resolution"
@@ -2024,7 +2019,7 @@ def load_digital_number(
var_name = "digital_number"
dn.attrs = {
- "comment": "%s digital number, %s" % (descr, comment),
+ "comment": f"{descr} digital number, {comment}",
"history": yaml.safe_dump(
{
var_name: get_glob(
=====================================
src/xradarsat2/tests/test_radarSat2_xarray_reader.py
=====================================
@@ -0,0 +1,442 @@
+import unittest
+from unittest.mock import Mock, patch, mock_open, MagicMock
+import numpy as np
+import xarray as xr
+from datetime import datetime
+import os
+import tempfile
+
+# Import functions from the module
+from xradarsat2.radarSat2_xarray_reader import (
+ xpath_get,
+ parse_value,
+ create_2d_matrix,
+ get_line_and_pix_info,
+ fill_image_attribute,
+ sort_list_files_and_get_pols,
+ get_glob,
+ get_product_attributes,
+ get_satellite_height,
+ get_satellite_pass_direction,
+ create_dic_geolocation_grid,
+ get_dic_orbit_information,
+ get_dic_attitude_info,
+ get_dict_doppler_centroid,
+ get_dic_doppler_rate_values,
+ get_dict_chirp,
+ get_dict_radar_parameters,
+)
+
+
+class TestXpathGet(unittest.TestCase):
+ """Test cases for xpath_get function"""
+
+ def test_xpath_get_valid_path(self):
+ test_dict = {"product": {"sourceAttributes": {"satellite": "RS2"}}}
+ result = xpath_get(test_dict, "/product/sourceAttributes/satellite")
+ self.assertEqual(result, "RS2")
+
+ def test_xpath_get_nested_dict(self):
+ test_dict = {"a": {"b": {"c": "value"}}}
+ result = xpath_get(test_dict, "/a/b/c")
+ self.assertEqual(result, "value")
+
+ def test_xpath_get_invalid_path(self):
+ test_dict = {"product": {"sourceAttributes": {"satellite": "RS2"}}}
+ result = xpath_get(test_dict, "/product/invalid/path")
+ self.assertIsNone(result)
+
+ def test_xpath_get_empty_dict(self):
+ result = xpath_get({}, "/some/path")
+ self.assertIsNone(result)
+
+
+class TestParseValue(unittest.TestCase):
+ """Test cases for parse_value function"""
+
+ def test_parse_integer(self):
+ self.assertEqual(parse_value("123"), 123)
+
+ def test_parse_float(self):
+ self.assertEqual(parse_value("123.456"), 123.456)
+
+ def test_parse_boolean(self):
+ self.assertTrue(parse_value("True"))
+ self.assertFalse(parse_value("False"))
+
+ def test_parse_list(self):
+ self.assertEqual(parse_value("[1, 2, 3]"), [1, 2, 3])
+
+ def test_parse_string(self):
+ self.assertEqual(parse_value("hello world"), "hello world")
+
+ def test_parse_dict(self):
+ result = parse_value("{'key': 'value'}")
+ self.assertEqual(result, {'key': 'value'})
+
+
+class TestCreate2DMatrix(unittest.TestCase):
+ """Test cases for create_2d_matrix function"""
+
+ def test_create_simple_matrix(self):
+ lines = [0, 0, 1, 1]
+ cols = [0, 1, 0, 1]
+ vals = [1.0, 2.0, 3.0, 4.0]
+ result = create_2d_matrix(lines, cols, vals)
+ expected = np.array([[1.0, 2.0], [3.0, 4.0]])
+ np.testing.assert_array_equal(result, expected)
+
+ def test_create_matrix_with_gaps(self):
+ lines = [0, 0, 2, 2]
+ cols = [0, 1, 0, 1]
+ vals = [1.0, 2.0, 3.0, 4.0]
+ result = create_2d_matrix(lines, cols, vals)
+ self.assertEqual(result.shape, (2, 2))
+
+ def test_create_matrix_unsorted(self):
+ lines = [1, 0, 1, 0]
+ cols = [1, 0, 0, 1]
+ vals = [4.0, 1.0, 3.0, 2.0]
+ result = create_2d_matrix(lines, cols, vals)
+ expected = np.array([[1.0, 2.0], [3.0, 4.0]])
+ np.testing.assert_array_equal(result, expected)
+
+
+class TestGetLineAndPixInfo(unittest.TestCase):
+ """Test cases for get_line_and_pix_info function"""
+
+ def test_get_line_and_pix_info(self):
+ dictio = {
+ "PixelSpacing": 12.5,
+ "LineSpacing": 10.0,
+ "someOtherKey": "value",
+ "anotherPixelSpacing": 15.0
+ }
+ result = get_line_and_pix_info(dictio)
+ self.assertIn("line", result)
+ self.assertIn("pixel", result)
+ self.assertIn("LineSpacing", result["line"])
+ self.assertIn("PixelSpacing", result["pixel"])
+ self.assertIn("anotherPixelSpacing", result["pixel"])
+
+
+class TestFillImageAttribute(unittest.TestCase):
+ """Test cases for fill_image_attribute function"""
+
+ def test_fill_image_attribute_basic(self):
+ dictio = {
+ "product": {
+ "imageAttributes": {
+ "someKey": "someValue",
+ "rasterAttributes": {
+ "numberOfLines": "1000",
+ "numberOfSamplesPerLine": "2000"
+ }
+ }
+ }
+ }
+ result = fill_image_attribute(dictio)
+ self.assertIn("rasterAttributes_numberOfLines", result)
+ self.assertIn("rasterAttributes_numberOfSamplesPerLine", result)
+
+
+class TestSortListFilesAndGetPols(unittest.TestCase):
+ """Test cases for sort_list_files_and_get_pols function"""
+
+ def test_sort_cross_pol_first(self):
+ list_tiff = ["/path/to/imagery_HV.tif", "/path/to/imagery_HH.tif"]
+ sorted_files, pols = sort_list_files_and_get_pols(list_tiff)
+ self.assertEqual(pols, ["HH", "HV"])
+ self.assertTrue(sorted_files[0].endswith("HH.tif"))
+
+ def test_single_pol(self):
+ list_tiff = ["/path/to/imagery_VV.tif"]
+ sorted_files, pols = sort_list_files_and_get_pols(list_tiff)
+ self.assertEqual(pols, ["VV"])
+ self.assertEqual(len(sorted_files), 1)
+
+ def test_dual_pol_already_sorted(self):
+ list_tiff = ["/path/to/imagery_HH.tif", "/path/to/imagery_HV.tif"]
+ sorted_files, pols = sort_list_files_and_get_pols(list_tiff)
+ self.assertEqual(pols, ["HH", "HV"])
+
+
+class TestGetGlob(unittest.TestCase):
+ """Test cases for get_glob function"""
+
+ def test_get_glob_identical(self):
+ strlist = ["file.txt", "file.txt"]
+ result = get_glob(strlist)
+ self.assertEqual(result, "file.txt")
+
+ def test_get_glob_different_chars(self):
+ strlist = ["file1.txt", "file2.txt", "file3.txt"]
+ result = get_glob(strlist)
+ self.assertIn("*", result)
+
+ def test_get_glob_prefix_suffix(self):
+ strlist = ["prefix_001_suffix", "prefix_002_suffix", "prefix_003_suffix"]
+ result = get_glob(strlist)
+ self.assertTrue(result.startswith("prefix"))
+ self.assertTrue(result.endswith("suffix"))
+
+
+class TestGetProductAttributes(unittest.TestCase):
+ """Test cases for get_product_attributes function"""
+
+ def test_get_product_attributes(self):
+ dic = {
+ "product": {
+ "sourceAttributes": {
+ "satellite": "RADARSAT-2",
+ "inputDatasetId": "12345",
+ "rawDataStartTime": "2020-01-01T12:00:00.000000Z",
+ "radarParameters": {}
+ }
+ }
+ }
+ result = get_product_attributes(dic)
+ self.assertEqual(result["satellite"], "RADARSAT-2")
+ self.assertEqual(result["inputDatasetId"], "12345")
+ self.assertIn("rawDataStartTime", result)
+ # Check if it's a numpy datetime64 (can be array or scalar)
+ self.assertTrue(isinstance(result["rawDataStartTime"], (np.datetime64, np.ndarray)))
+ if isinstance(result["rawDataStartTime"], np.ndarray):
+ self.assertEqual(result["rawDataStartTime"].dtype, np.dtype('datetime64[ns]'))
+
+
+class TestGetSatelliteHeight(unittest.TestCase):
+ """Test cases for get_satellite_height function"""
+
+ def test_get_satellite_height(self):
+ dic = {
+ "product": {
+ "imageGenerationParameters": {
+ "sarProcessingInformation": {
+ "satelliteHeight": {
+ "#text": "798000.0",
+ "@units": "m"
+ }
+ }
+ }
+ }
+ }
+ result = get_satellite_height(dic)
+ self.assertIn("satelliteHeight", result)
+ self.assertEqual(result["satelliteHeight"], 798000.0)
+ self.assertIn("satelliteHeight_units", result)
+ self.assertEqual(result["satelliteHeight_units"], "m")
+
+
+class TestGetSatellitePassDirection(unittest.TestCase):
+ """Test cases for get_satellite_pass_direction function"""
+
+ def test_get_pass_direction_ascending(self):
+ dic = {
+ "product": {
+ "sourceAttributes": {
+ "orbitAndAttitude": {
+ "orbitInformation": {
+ "passDirection": "Ascending"
+ }
+ }
+ }
+ }
+ }
+ result = get_satellite_pass_direction(dic)
+ self.assertEqual(result["passDirection"], "Ascending")
+
+ def test_get_pass_direction_descending(self):
+ dic = {
+ "product": {
+ "sourceAttributes": {
+ "orbitAndAttitude": {
+ "orbitInformation": {
+ "passDirection": "Descending"
+ }
+ }
+ }
+ }
+ }
+ result = get_satellite_pass_direction(dic)
+ self.assertEqual(result["passDirection"], "Descending")
+
+
+class TestCreateDicGeolocationGrid(unittest.TestCase):
+ """Test cases for create_dic_geolocation_grid function"""
+
+ def test_create_dic_geolocation_grid(self):
+ dictio = {
+ "product": {
+ "imageAttributes": {
+ "geographicInformation": {
+ "geolocationGrid": {
+ "imageTiePoint": [
+ {
+ "imageCoordinate": {
+ "line": "0",
+ "pixel": "0"
+ },
+ "geodeticCoordinate": {
+ "latitude": {"#text": "45.0", "@units": "deg"},
+ "longitude": {"#text": "-75.0", "@units": "deg"},
+ "height": {"#text": "0.0", "@units": "m"}
+ }
+ }
+ ]
+ }
+ },
+ "rasterAttributes": {
+ "numberOfLines": "100",
+ "numberOfSamplesPerLine": "100"
+ }
+ }
+ }
+ }
+ result = create_dic_geolocation_grid(dictio)
+ self.assertIn("latitude", result)
+ self.assertIn("longitude", result)
+ self.assertIn("height", result)
+ self.assertIn("coords", result)
+ self.assertEqual(len(result["latitude"]["values"]), 1)
+ self.assertEqual(result["latitude"]["values"][0], 45.0)
+
+
+class TestGetDicOrbitInformation(unittest.TestCase):
+ """Test cases for get_dic_orbit_information function"""
+
+ def test_get_dic_orbit_information(self):
+ dictio = {
+ "product": {
+ "sourceAttributes": {
+ "orbitAndAttitude": {
+ "orbitInformation": {
+ "passDirection": "Ascending",
+ "stateVector": [
+ {
+ "timeStamp": "2020-01-01T12:00:00.000000Z",
+ "xPosition": {"#text": "1000000.0", "@units": "m"},
+ "yPosition": {"#text": "2000000.0", "@units": "m"},
+ "zPosition": {"#text": "3000000.0", "@units": "m"},
+ "xVelocity": {"#text": "100.0", "@units": "m/s"},
+ "yVelocity": {"#text": "200.0", "@units": "m/s"},
+ "zVelocity": {"#text": "300.0", "@units": "m/s"}
+ }
+ ]
+ }
+ }
+ }
+ }
+ }
+ result = get_dic_orbit_information(dictio)
+ self.assertIn("ds_attr", result)
+ self.assertIn("timestamp", result)
+ self.assertIn("xPosition", result)
+ self.assertEqual(len(result["xPosition"]["values"]), 1)
+ self.assertEqual(result["xPosition"]["values"][0], 1000000.0)
+
+
+class TestGetDicAttitudeInfo(unittest.TestCase):
+ """Test cases for get_dic_attitude_info function"""
+
+ def test_get_dic_attitude_info(self):
+ dictio = {
+ "product": {
+ "sourceAttributes": {
+ "orbitAndAttitude": {
+ "attitudeInformation": {
+ "attitudeAngles": [
+ {
+ "timeStamp": "2020-01-01T12:00:00.000000Z",
+ "yaw": {"#text": "0.5", "@units": "deg"},
+ "roll": {"#text": "0.3", "@units": "deg"},
+ "pitch": {"#text": "0.2", "@units": "deg"}
+ }
+ ]
+ }
+ }
+ }
+ }
+ }
+ result = get_dic_attitude_info(dictio)
+ self.assertIn("ds_attr", result)
+ self.assertIn("timestamp", result)
+ self.assertIn("yaw", result)
+ self.assertIn("roll", result)
+ self.assertIn("pitch", result)
+ self.assertEqual(len(result["yaw"]["values"]), 1)
+
+
+class TestGetDictDopplerCentroid(unittest.TestCase):
+ """Test cases for get_dict_doppler_centroid function"""
+
+ def test_get_dict_doppler_centroid(self):
+ dictio = {
+ "product": {
+ "imageGenerationParameters": {
+ "dopplerCentroid": [
+ {
+ "timeOfDopplerCentroidEstimate": "2020-01-01T12:00:00.000000Z",
+ "dopplerAmbiguity": "0",
+ "dopplerAmbiguityConfidence": "1.0",
+ "dopplerCentroidReferenceTime": {"#text": "0.5", "@units": "s"},
+ "dopplerCentroidPolynomialPeriod": {"#text": "1.0", "@units": "s"},
+ "dopplerCentroidCoefficients": "1.0 2.0 3.0",
+ "dopplerCentroidConfidence": "0.95"
+ }
+ ]
+ }
+ }
+ }
+ result = get_dict_doppler_centroid(dictio)
+ self.assertIn("timeOfDopplerCentroidEstimate", result)
+ self.assertIn("dopplerAmbiguity", result)
+ self.assertEqual(len(result["dopplerCentroidCoefficients"]["values"]), 1)
+ self.assertEqual(len(result["dopplerCentroidCoefficients"]["values"][0]), 3)
+
+
+class TestGetDicDopplerRateValues(unittest.TestCase):
+ """Test cases for get_dic_doppler_rate_values function"""
+
+ def test_get_dic_doppler_rate_values_dict(self):
+ dictio = {
+ "product": {
+ "imageGenerationParameters": {
+ "dopplerRateValues": {
+ "dopplerRateReferenceTime": {"#text": "0.5", "@units": "s"},
+ "dopplerRateValuesCoefficients": "1.0 2.0 3.0"
+ }
+ }
+ }
+ }
+ result = get_dic_doppler_rate_values(dictio)
+ self.assertIn("dopplerRateReferenceTime", result)
+ self.assertIn("dopplerRateValuesCoefficients", result)
+ self.assertEqual(len(result["dopplerRateValuesCoefficients"]["values"]), 1)
+
+
+class TestGetDictRadarParameters(unittest.TestCase):
+ """Test cases for get_dict_radar_parameters function"""
+
+ def test_get_dict_radar_parameters(self):
+ dictio = {
+ "product": {
+ "sourceAttributes": {
+ "radarParameters": {
+ "acquisitionType": "Wide",
+ "beams": "W1 W2",
+ "polarizations": "HH HV",
+ "radarCenterFrequency": "5405000000.0"
+ }
+ }
+ }
+ }
+ result = get_dict_radar_parameters(dictio)
+ self.assertIn("ds_attr", result)
+ self.assertEqual(result["ds_attr"]["acquisitionType"], "Wide")
+ self.assertEqual(result["ds_attr"]["beams"], ["W1", "W2"])
+ self.assertEqual(result["ds_attr"]["polarizations"], ["HH", "HV"])
+
+
+if __name__ == "__main__":
+ unittest.main()
=====================================
src/xradarsat2/utils.py
=====================================
@@ -1,8 +1,14 @@
-import xradarsat2
import logging
import os
-import yaml
import re
+import warnings
+import zipfile
+
+import aiohttp
+import fsspec
+import yaml
+
+import xradarsat2
def get_glob(strlist):
@@ -42,6 +48,76 @@ def load_config():
)
logging.info("config path: %s", config_path)
- stream = open(config_path, "r")
+ stream = open(config_path)
conf = yaml.load(stream, Loader=yaml.CLoader)
return conf
+
+
+def get_test_file(fname):
+ """
+ get test file from https://cyclobs.ifremer.fr/static/sarwing_datarmor/xsardata/
+ file is unzipped and extracted to `config['data_dir']`
+
+ Parameters
+ ----------
+ fname: str
+ file name to get (without '.zip' extension)
+
+ Returns
+ -------
+ str
+ path to file, relative to `config['data_dir']`
+
+ """
+ config = {"data_dir": "/tmp"}
+
+ def url_get(url, cache_dir=os.path.join(config["data_dir"], "fsspec_cache")):
+ """
+ Get fil from url, using caching.
+
+ Parameters
+ ----------
+ url: str
+ cache_dir: str
+ Cache dir to use. default to `os.path.join(config['data_dir'], 'fsspec_cache')`
+
+ Raises
+ ------
+ FileNotFoundError
+
+ Returns
+ -------
+ filename: str
+ The local file name
+
+ Notes
+ -----
+ Due to fsspec, the returned filename won't match the remote one.
+ """
+
+ if "://" in url:
+ with fsspec.open(
+ f"filecache::{url}",
+ https={"client_kwargs": {"timeout": aiohttp.ClientTimeout(total=3600)}},
+ filecache={
+ "cache_storage": os.path.join(
+ os.path.join(config["data_dir"], "fsspec_cache")
+ )
+ },
+ ) as f:
+ fname = f.name
+ else:
+ fname = url
+
+ return fname
+
+ res_path = config["data_dir"]
+ base_url = "https://cyclobs.ifremer.fr/static/sarwing_datarmor/xsardata"
+ file_url = f"{base_url}/{fname}.zip"
+ if not os.path.exists(os.path.join(res_path, fname)):
+ warnings.warn(f"Downloading {file_url}")
+ local_file = url_get(file_url)
+ warnings.warn(f"Unzipping {os.path.join(res_path, fname)}")
+ with zipfile.ZipFile(local_file, "r") as zip_ref:
+ zip_ref.extractall(res_path)
+ return os.path.join(res_path, fname)
=====================================
src/xradarsat2/xradarsat2-config.yaml
=====================================
@@ -1 +1 @@
-folder_path: ./2021/137/RS2_OK129673_PK1136693_DK1093025_SCWA_20210517_010235_VV_VH_SGF
+folder_path: RS2_OK135107_PK1187782_DK1151894_SCWA_20220407_182127_VV_VH_SGF
=====================================
test/test_opening_datatree_radarsat2.py
=====================================
@@ -1,7 +1,8 @@
-from xradarsat2.utils import load_config
-import xradarsat2
-import time
import logging
+import time
+
+import xradarsat2
+from xradarsat2.utils import get_test_file, load_config
logging.basicConfig(level=logging.DEBUG)
logging.debug("start opening RadarSAT-2 product")
@@ -11,13 +12,14 @@ logging.debug("start opening RadarSAT-2 product")
t0 = time.time()
conf = load_config()
folder_path = conf["folder_path"]
-dt = xradarsat2.rs2_reader(folder_path)
+rs2_product_path = get_test_file(conf["folder_path"])
+dt = xradarsat2.rs2_reader(rs2_product_path)
elapse_t = time.time() - t0
print(type(dt), dt)
print("out of the reader")
print(dt)
-print("time to read the SAFE through nfs: %1.2f sec" % elapse_t)
+print(f"time to read the SAFE through nfs: {elapse_t:1.2f} sec")
dt = xradarsat2.load_digital_number(
dt, chunks={"pol": "VV", "line": 6000, "sample": 8000}
)
View it on GitLab: https://salsa.debian.org/debian-gis-team/xradarsat2/-/commit/9c5bd2534b2f3480fcf9536606192fc55f582efe
--
View it on GitLab: https://salsa.debian.org/debian-gis-team/xradarsat2/-/commit/9c5bd2534b2f3480fcf9536606192fc55f582efe
You're receiving this email because of your account on salsa.debian.org.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://alioth-lists.debian.net/pipermail/pkg-grass-devel/attachments/20251023/607c6d5e/attachment-0001.htm>
More information about the Pkg-grass-devel
mailing list