GRSdriver 2.0.2__tar.gz → 2.0.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {grsdriver-2.0.2 → grsdriver-2.0.4}/GRSdriver/__init__.py +8 -1
- {grsdriver-2.0.2 → grsdriver-2.0.4}/GRSdriver/driver_landsat_col2.py +32 -14
- {grsdriver-2.0.2 → grsdriver-2.0.4}/GRSdriver/run.py +0 -1
- {grsdriver-2.0.2 → grsdriver-2.0.4}/GRSdriver/utils.py +28 -0
- {grsdriver-2.0.2 → grsdriver-2.0.4}/GRSdriver.egg-info/PKG-INFO +56 -22
- {grsdriver-2.0.2 → grsdriver-2.0.4}/GRSdriver.egg-info/requires.txt +16 -4
- {grsdriver-2.0.2 → grsdriver-2.0.4}/PKG-INFO +56 -22
- {grsdriver-2.0.2 → grsdriver-2.0.4}/README.md +41 -17
- {grsdriver-2.0.2 → grsdriver-2.0.4}/pyproject.toml +6 -1
- {grsdriver-2.0.2 → grsdriver-2.0.4}/tests/test_landsat_driver.py +16 -0
- {grsdriver-2.0.2 → grsdriver-2.0.4}/tests/test_utils.py +29 -0
- {grsdriver-2.0.2 → grsdriver-2.0.4}/GRSdriver/driver_S2_SAFE.py +0 -0
- {grsdriver-2.0.2 → grsdriver-2.0.4}/GRSdriver/rsr/__init__.py +0 -0
- {grsdriver-2.0.2 → grsdriver-2.0.4}/GRSdriver/rsr/data/L4_TM_RSR.xlsx +0 -0
- {grsdriver-2.0.2 → grsdriver-2.0.4}/GRSdriver/rsr/data/L5_TM_RSR.xlsx +0 -0
- {grsdriver-2.0.2 → grsdriver-2.0.4}/GRSdriver/rsr/data/L7_RSR.xlsx +0 -0
- {grsdriver-2.0.2 → grsdriver-2.0.4}/GRSdriver/rsr/data/L8_Ball_BA_RSR.v1.2.xlsx +0 -0
- {grsdriver-2.0.2 → grsdriver-2.0.4}/GRSdriver/rsr/data/L8_TIRS_Relative_Spectral_Responses.BA_.v1.xlsx +0 -0
- {grsdriver-2.0.2 → grsdriver-2.0.4}/GRSdriver/rsr/data/L9_OLI2_Ball_FPM_RSR.v1.0.xlsx +0 -0
- {grsdriver-2.0.2 → grsdriver-2.0.4}/GRSdriver/rsr/data/L9_TIRS2_Relative_Spectral_Responses.BA.v1.0.xlsx +0 -0
- {grsdriver-2.0.2 → grsdriver-2.0.4}/GRSdriver/rsr/data/Sentinel-2_MSI_Spectral_Responses.xlsx +0 -0
- {grsdriver-2.0.2 → grsdriver-2.0.4}/GRSdriver/rsr/data/__init__.py +0 -0
- {grsdriver-2.0.2 → grsdriver-2.0.4}/GRSdriver/rsr/data/rsr_landsat_8_oli.nc +0 -0
- {grsdriver-2.0.2 → grsdriver-2.0.4}/GRSdriver/rsr/data/rsr_landsat_8_oli_tirs.nc +0 -0
- {grsdriver-2.0.2 → grsdriver-2.0.4}/GRSdriver/rsr/data/rsr_landsat_8_tirs.nc +0 -0
- {grsdriver-2.0.2 → grsdriver-2.0.4}/GRSdriver/rsr/data/rsr_landsat_9_oli.nc +0 -0
- {grsdriver-2.0.2 → grsdriver-2.0.4}/GRSdriver/rsr/data/rsr_landsat_9_oli_tirs.nc +0 -0
- {grsdriver-2.0.2 → grsdriver-2.0.4}/GRSdriver/rsr/data/rsr_landsat_9_tirs.nc +0 -0
- {grsdriver-2.0.2 → grsdriver-2.0.4}/GRSdriver/rsr/landsat_formatting.py +0 -0
- {grsdriver-2.0.2 → grsdriver-2.0.4}/GRSdriver/visual.py +0 -0
- {grsdriver-2.0.2 → grsdriver-2.0.4}/GRSdriver.egg-info/SOURCES.txt +0 -0
- {grsdriver-2.0.2 → grsdriver-2.0.4}/GRSdriver.egg-info/dependency_links.txt +0 -0
- {grsdriver-2.0.2 → grsdriver-2.0.4}/GRSdriver.egg-info/entry_points.txt +0 -0
- {grsdriver-2.0.2 → grsdriver-2.0.4}/GRSdriver.egg-info/top_level.txt +0 -0
- {grsdriver-2.0.2 → grsdriver-2.0.4}/LICENSE +0 -0
- {grsdriver-2.0.2 → grsdriver-2.0.4}/requirements.txt +0 -0
- {grsdriver-2.0.2 → grsdriver-2.0.4}/setup.cfg +0 -0
- {grsdriver-2.0.2 → grsdriver-2.0.4}/tests/test_package.py +0 -0
- {grsdriver-2.0.2 → grsdriver-2.0.4}/tests/test_s2_driver.py +0 -0
|
@@ -88,7 +88,14 @@ equations.
|
|
|
88
88
|
# v2.0.2 (2026-10-16):
|
|
89
89
|
# continuous integration (tests with Python 3.9 to 3.13, PyPI publishing on tags);
|
|
90
90
|
# grs_driver --help without matplotlib/cartopy
|
|
91
|
-
|
|
91
|
+
#
|
|
92
|
+
# v2.0.3 (2026-10-16):
|
|
93
|
+
# README for 2.0, optional extras viz and tutorials, minimal environment.yml
|
|
94
|
+
#
|
|
95
|
+
# v2.0.4 (2026-10-17):
|
|
96
|
+
# direct reading of the bands on their own grid (~20-25 % faster on full scenes),
|
|
97
|
+
# Landsat angles in float32 (also interpolated and in dask mode), automatic GitHub release
|
|
98
|
+
__version__='2.0.4'
|
|
92
99
|
__package__ ='GRSdriver'
|
|
93
100
|
|
|
94
101
|
from .driver_landsat_col2 import LandsatDriver
|
|
@@ -18,8 +18,10 @@ Example::
|
|
|
18
18
|
import datetime
|
|
19
19
|
import glob
|
|
20
20
|
import os
|
|
21
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
21
22
|
|
|
22
23
|
import numpy as np
|
|
24
|
+
import dask.array as da
|
|
23
25
|
import pandas as pd
|
|
24
26
|
import geopandas as gpd
|
|
25
27
|
import xarray as xr
|
|
@@ -254,9 +256,11 @@ class LandsatDriver():
|
|
|
254
256
|
# convert band from normalized radiance into reflectance
|
|
255
257
|
# vis-NIR-SWIR bands only (upper limit 3000nm)
|
|
256
258
|
# -----------------------------------------------------------
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
259
|
+
bands = self.prod.bands
|
|
260
|
+
mus = np.cos(np.radians(self.prod['sza'])).astype(bands.dtype)
|
|
261
|
+
# DataArray.where rather than xr.where, which computes float64 on dask arrays (float32 declared)
|
|
262
|
+
self.prod['bands'] = bands.where(self.prod.wl > 3000, bands / mus).astype(bands.dtype) \
|
|
263
|
+
.transpose(*bands.dims)
|
|
260
264
|
|
|
261
265
|
|
|
262
266
|
self.prod.attrs = self.prod.attrs
|
|
@@ -301,7 +305,19 @@ class LandsatDriver():
|
|
|
301
305
|
margin = 2 * max(abs(float(ds.x[1] - ds.x[0])), abs(float(ds.y[1] - ds.y[0])))
|
|
302
306
|
ds = ds.sel(x=slice(x.min() - margin, x.max() + margin),
|
|
303
307
|
y=slice(y.max() + margin, y.min() - margin))
|
|
308
|
+
dtypes = {var: ds[var].dtype for var in ds.data_vars}
|
|
304
309
|
ds = ds.interp(x=self.prod.x, y=self.prod.y, method=method)
|
|
310
|
+
# interp computes in float64 (on dask arrays while declaring the input dtype):
|
|
311
|
+
# float variables back to their dtype (e.g. float32 angles)
|
|
312
|
+
for var, dtype in dtypes.items():
|
|
313
|
+
if dtype.kind == 'f':
|
|
314
|
+
data = ds[var].data
|
|
315
|
+
if isinstance(data, da.Array):
|
|
316
|
+
data = data.map_blocks(lambda block, dtype=dtype: block.astype(dtype, copy=False),
|
|
317
|
+
dtype=dtype)
|
|
318
|
+
else:
|
|
319
|
+
data = data.astype(dtype, copy=False)
|
|
320
|
+
ds[var] = ds[var].copy(data=data)
|
|
305
321
|
if self.prod.bands.chunks is not None:
|
|
306
322
|
ds = ds.chunk({dim: self.prod.bands.chunksizes[dim] for dim in ('y', 'x')})
|
|
307
323
|
return ds
|
|
@@ -463,18 +479,20 @@ class LandsatDriver():
|
|
|
463
479
|
|
|
464
480
|
# lazy reading in dask mode
|
|
465
481
|
chunks = DASK_CHUNKS if use_dask() else None
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
raster.
|
|
474
|
-
|
|
475
|
-
|
|
482
|
+
|
|
483
|
+
def read_angle(suffix):
|
|
484
|
+
# native grid of the angle file, read as float32 (exact for the stored integers)
|
|
485
|
+
ang_file = glob.glob(opj(self.abspath, '*' + suffix + '.TIF'))[0]
|
|
486
|
+
with rasterio.open(ang_file) as src:
|
|
487
|
+
grid = (src.transform, src.width, src.height)
|
|
488
|
+
raster = read_on_grid(ang_file, *grid, nodata=nodata, dtype='float32', chunks=chunks)
|
|
489
|
+
return raster.where(raster != nodata) * np.float32(scale_factor)
|
|
490
|
+
|
|
491
|
+
with ThreadPoolExecutor(max_workers=max(1, self.n_workers)) as executor:
|
|
492
|
+
rasters = list(executor.map(read_angle, [suffix for _, suffix in geoms]))
|
|
493
|
+
geom = xr.Dataset({name: raster for (name, _), raster in zip(geoms, rasters)})
|
|
476
494
|
geom['raa'] = (geom['saa'] - geom['vaa']) # %360
|
|
477
|
-
self.geom = geom.drop_vars(['vaa', 'saa'
|
|
495
|
+
self.geom = geom.drop_vars(['vaa', 'saa'])
|
|
478
496
|
|
|
479
497
|
@staticmethod
|
|
480
498
|
def scat_angle(sza, vza, azi):
|
|
@@ -49,11 +49,39 @@ def use_dask():
|
|
|
49
49
|
return value.lower() in ('1', 'true')
|
|
50
50
|
|
|
51
51
|
|
|
52
|
+
def _pixel_offset(src_transform, transform):
|
|
53
|
+
'''
|
|
54
|
+
(row, col) offset of the target grid in the source grid when both grids coincide (same
|
|
55
|
+
pixel size, north up, target origin on a source pixel corner), else None.
|
|
56
|
+
'''
|
|
57
|
+
if not (np.isclose(transform.a, src_transform.a) and np.isclose(transform.e, src_transform.e)
|
|
58
|
+
and transform.b == transform.d == src_transform.b == src_transform.d == 0):
|
|
59
|
+
return None
|
|
60
|
+
col = (transform.c - src_transform.c) / src_transform.a
|
|
61
|
+
row = (transform.f - src_transform.f) / src_transform.e
|
|
62
|
+
if abs(col - round(col)) > 1e-6 or abs(row - round(row)) > 1e-6:
|
|
63
|
+
return None
|
|
64
|
+
return round(row), round(col)
|
|
65
|
+
|
|
66
|
+
|
|
52
67
|
def _read_window(path, transform, width, height, resampling, nodata, dtype):
|
|
53
68
|
'''
|
|
54
69
|
Read the first band of ``path`` on a target grid, as a numpy array (see :func:`read_on_grid`).
|
|
55
70
|
'''
|
|
56
71
|
with rasterio.open(path) as src:
|
|
72
|
+
offset = _pixel_offset(src.transform, transform)
|
|
73
|
+
if offset is not None:
|
|
74
|
+
# target grid = part of the source grid: direct copy, no resampling needed
|
|
75
|
+
row, col = offset
|
|
76
|
+
src_nodata = src.nodata if nodata is None else nodata
|
|
77
|
+
data = np.full((height, width), 0 if src_nodata is None else src_nodata,
|
|
78
|
+
dtype=dtype or src.dtypes[0])
|
|
79
|
+
r0, c0 = max(row, 0), max(col, 0)
|
|
80
|
+
r1, c1 = min(row + height, src.height), min(col + width, src.width)
|
|
81
|
+
if r1 > r0 and c1 > c0:
|
|
82
|
+
window = rasterio.windows.Window(c0, r0, c1 - c0, r1 - r0)
|
|
83
|
+
data[r0 - row:r1 - row, c0 - col:c1 - col] = src.read(1, window=window)
|
|
84
|
+
return data
|
|
57
85
|
# source window covering the target grid (north up), with a margin for the
|
|
58
86
|
# resampling kernel
|
|
59
87
|
minx, maxx = transform.c, transform.c + transform.a * width
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: GRSdriver
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.4
|
|
4
4
|
Summary: Driver for L1C satellite images dedicated to GRS processor
|
|
5
5
|
Author-email: Tristan Harmel <tristan.harmel@gmail.com>
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -33,14 +33,24 @@ Requires-Dist: xarray
|
|
|
33
33
|
Requires-Dist: shapely
|
|
34
34
|
Requires-Dist: geopandas>=1.0
|
|
35
35
|
Provides-Extra: dev
|
|
36
|
-
Requires-Dist: black; extra == "dev"
|
|
37
|
-
Requires-Dist: bumpver; extra == "dev"
|
|
38
|
-
Requires-Dist: isort; extra == "dev"
|
|
39
|
-
Requires-Dist: pip-tools; extra == "dev"
|
|
40
36
|
Requires-Dist: pytest; extra == "dev"
|
|
37
|
+
Requires-Dist: build; extra == "dev"
|
|
38
|
+
Requires-Dist: twine; extra == "dev"
|
|
41
39
|
Provides-Extra: cli
|
|
42
40
|
Requires-Dist: matplotlib; extra == "cli"
|
|
43
41
|
Requires-Dist: cartopy; extra == "cli"
|
|
42
|
+
Provides-Extra: viz
|
|
43
|
+
Requires-Dist: matplotlib; extra == "viz"
|
|
44
|
+
Requires-Dist: holoviews; extra == "viz"
|
|
45
|
+
Requires-Dist: datashader; extra == "viz"
|
|
46
|
+
Requires-Dist: bokeh; extra == "viz"
|
|
47
|
+
Requires-Dist: colorcet; extra == "viz"
|
|
48
|
+
Requires-Dist: panel; extra == "viz"
|
|
49
|
+
Provides-Extra: tutorials
|
|
50
|
+
Requires-Dist: jupyterlab; extra == "tutorials"
|
|
51
|
+
Requires-Dist: matplotlib; extra == "tutorials"
|
|
52
|
+
Requires-Dist: cartopy; extra == "tutorials"
|
|
53
|
+
Requires-Dist: requests; extra == "tutorials"
|
|
44
54
|
Dynamic: license-file
|
|
45
55
|
|
|
46
56
|
<img src="https://raw.githubusercontent.com/Tristanovsk/GRSdriver/master/docs/source/_static/GRS_driver.png" alt="GRSdriver_icon" width="100"/>
|
|
@@ -49,7 +59,10 @@ Dynamic: license-file
|
|
|
49
59
|
## Driver dedicated to load and prepare data for the GRS processor
|
|
50
60
|
|
|
51
61
|
[](https://pypi.org/project/GRSdriver/)
|
|
62
|
+
[](https://pypi.org/project/GRSdriver/)
|
|
63
|
+
[](https://github.com/Tristanovsk/GRSdriver/actions/workflows/tests.yml)
|
|
52
64
|
[](https://grsdriver.readthedocs.io)
|
|
65
|
+
[](https://github.com/Tristanovsk/GRSdriver/blob/master/LICENSE)
|
|
53
66
|
|
|
54
67
|
GRSdriver loads a Level-1 satellite image into a single `xarray.Dataset` ready for the
|
|
55
68
|
[GRS](https://github.com/CNES/GRSprocessor) atmospheric and sunglint correction:
|
|
@@ -59,37 +72,49 @@ GRSdriver loads a Level-1 satellite image into a single `xarray.Dataset` ready f
|
|
|
59
72
|
- Landsat 8 and 9 Collection-2 L1: reflectance, brightness temperature of the thermal bands,
|
|
60
73
|
sun/viewing angles and L1 quality flags
|
|
61
74
|
- spectral response functions and central wavelengths of each band
|
|
62
|
-
- spatial subsetting, lazy (dask) loading and low memory options for full tiles
|
|
75
|
+
- spatial subsetting, parallel reading, lazy (dask) loading and low memory options for full tiles
|
|
63
76
|
- visualization tools
|
|
64
77
|
|
|
65
78
|
Full documentation: https://grsdriver.readthedocs.io
|
|
66
79
|
|
|
80
|
+
## What's new in 2.0
|
|
81
|
+
|
|
82
|
+
- **pip-only install**: EOReader and the GDAL Python bindings are no longer needed.
|
|
83
|
+
- **~2x faster**: full Sentinel-2 tile at 20 m in ~30 s (bands read and angles computed in
|
|
84
|
+
parallel), fully lazy dask mode in ~20 s.
|
|
85
|
+
- **Fixes**: Sentinel-2 angles of processing baselines < 4.0, alignment of the Landsat
|
|
86
|
+
panchromatic band, size of the `SpatioTemp` boxes.
|
|
87
|
+
- **Upgrading from 1.x**: the drivers no longer take `**kwargs` (they were passed to EOReader) and
|
|
88
|
+
have no `.reader` attribute; a `subset` given as a sequence is a bounding box
|
|
89
|
+
`(minx, miny, maxx, maxy)` in the product CRS. See the
|
|
90
|
+
[CHANGELOG](https://github.com/Tristanovsk/GRSdriver/blob/master/CHANGELOG.md).
|
|
91
|
+
|
|
67
92
|
## Installation
|
|
68
93
|
|
|
94
|
+
Python 3.9 to 3.13:
|
|
95
|
+
|
|
69
96
|
```commandline
|
|
70
97
|
pip install GRSdriver
|
|
71
98
|
```
|
|
72
99
|
|
|
73
100
|
No separate GDAL installation is needed: rasterio and geopandas ship with their own GDAL
|
|
74
|
-
(including the JPEG2000 driver for Sentinel-2).
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
101
|
+
(including the JPEG2000 driver for Sentinel-2). Optional extras:
|
|
102
|
+
|
|
103
|
+
| Extra | For | Install |
|
|
104
|
+
|---|---|---|
|
|
105
|
+
| `cli` | `grs_driver` command line (RGB quicklooks) | `pip install "GRSdriver[cli]"` |
|
|
106
|
+
| `viz` | interactive visualization tools (`GRSdriver.visual`) | `pip install "GRSdriver[viz]"` |
|
|
107
|
+
| `tutorials` | running the tutorial notebooks | `pip install "GRSdriver[tutorials]"` |
|
|
79
108
|
|
|
80
|
-
###
|
|
109
|
+
### From the repository
|
|
110
|
+
|
|
111
|
+
To run the tutorials or work on the code:
|
|
81
112
|
```commandline
|
|
82
113
|
git clone https://github.com/Tristanovsk/GRSdriver.git
|
|
83
114
|
cd GRSdriver
|
|
84
|
-
|
|
85
|
-
conda activate grs
|
|
86
|
-
pip install -e .
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
For the interactive visualization tools and the notebooks:
|
|
90
|
-
```commandline
|
|
91
|
-
conda install -c conda-forge jupyterlab holoviews panel datashader cartopy
|
|
115
|
+
pip install -e ".[cli,viz,tutorials,dev]"
|
|
92
116
|
```
|
|
117
|
+
or with conda: `conda env create -f environment.yml && conda activate grsdriver`.
|
|
93
118
|
|
|
94
119
|
## Quick start
|
|
95
120
|
|
|
@@ -104,6 +129,12 @@ prod = l1c.prod # bands, vza, raa, sza, SRF, wl_true + metadata
|
|
|
104
129
|
# Landsat 8/9
|
|
105
130
|
l1 = LandsatDriver('LC09_L1TP_..._02_T1', resolution=30)
|
|
106
131
|
l1.load_product()
|
|
132
|
+
|
|
133
|
+
# region of interest: any geometry (GeoSeries, any CRS), here a lon/lat box
|
|
134
|
+
import geopandas as gpd
|
|
135
|
+
from shapely.geometry import box
|
|
136
|
+
roi = gpd.GeoSeries([box(5.0, 43.38, 5.25, 43.58)], crs=4326)
|
|
137
|
+
l1c = Sentinel2Driver('S2B_MSIL1C_..._.SAFE', resolution=20, subset=roi)
|
|
107
138
|
```
|
|
108
139
|
|
|
109
140
|
The bands are read by 4 threads in parallel: set `n_workers` (e.g. `n_workers=1`) to trade speed
|
|
@@ -174,6 +205,9 @@ Viewing azimuth of each band computed by GRSdriver:
|
|
|
174
205
|
|
|
175
206
|

|
|
176
207
|
|
|
177
|
-
## Changes
|
|
208
|
+
## Changes and contributing
|
|
178
209
|
|
|
179
|
-
See [CHANGELOG
|
|
210
|
+
See the [CHANGELOG](https://github.com/Tristanovsk/GRSdriver/blob/master/CHANGELOG.md). Tests run
|
|
211
|
+
with `pytest tests` (the tests that need satellite products are skipped when they are not
|
|
212
|
+
available); the [contributing guide](https://grsdriver.readthedocs.io/en/latest/contributing.html)
|
|
213
|
+
explains how to update the tutorials and make a release.
|
|
@@ -19,8 +19,20 @@ matplotlib
|
|
|
19
19
|
cartopy
|
|
20
20
|
|
|
21
21
|
[dev]
|
|
22
|
-
black
|
|
23
|
-
bumpver
|
|
24
|
-
isort
|
|
25
|
-
pip-tools
|
|
26
22
|
pytest
|
|
23
|
+
build
|
|
24
|
+
twine
|
|
25
|
+
|
|
26
|
+
[tutorials]
|
|
27
|
+
jupyterlab
|
|
28
|
+
matplotlib
|
|
29
|
+
cartopy
|
|
30
|
+
requests
|
|
31
|
+
|
|
32
|
+
[viz]
|
|
33
|
+
matplotlib
|
|
34
|
+
holoviews
|
|
35
|
+
datashader
|
|
36
|
+
bokeh
|
|
37
|
+
colorcet
|
|
38
|
+
panel
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: GRSdriver
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.4
|
|
4
4
|
Summary: Driver for L1C satellite images dedicated to GRS processor
|
|
5
5
|
Author-email: Tristan Harmel <tristan.harmel@gmail.com>
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -33,14 +33,24 @@ Requires-Dist: xarray
|
|
|
33
33
|
Requires-Dist: shapely
|
|
34
34
|
Requires-Dist: geopandas>=1.0
|
|
35
35
|
Provides-Extra: dev
|
|
36
|
-
Requires-Dist: black; extra == "dev"
|
|
37
|
-
Requires-Dist: bumpver; extra == "dev"
|
|
38
|
-
Requires-Dist: isort; extra == "dev"
|
|
39
|
-
Requires-Dist: pip-tools; extra == "dev"
|
|
40
36
|
Requires-Dist: pytest; extra == "dev"
|
|
37
|
+
Requires-Dist: build; extra == "dev"
|
|
38
|
+
Requires-Dist: twine; extra == "dev"
|
|
41
39
|
Provides-Extra: cli
|
|
42
40
|
Requires-Dist: matplotlib; extra == "cli"
|
|
43
41
|
Requires-Dist: cartopy; extra == "cli"
|
|
42
|
+
Provides-Extra: viz
|
|
43
|
+
Requires-Dist: matplotlib; extra == "viz"
|
|
44
|
+
Requires-Dist: holoviews; extra == "viz"
|
|
45
|
+
Requires-Dist: datashader; extra == "viz"
|
|
46
|
+
Requires-Dist: bokeh; extra == "viz"
|
|
47
|
+
Requires-Dist: colorcet; extra == "viz"
|
|
48
|
+
Requires-Dist: panel; extra == "viz"
|
|
49
|
+
Provides-Extra: tutorials
|
|
50
|
+
Requires-Dist: jupyterlab; extra == "tutorials"
|
|
51
|
+
Requires-Dist: matplotlib; extra == "tutorials"
|
|
52
|
+
Requires-Dist: cartopy; extra == "tutorials"
|
|
53
|
+
Requires-Dist: requests; extra == "tutorials"
|
|
44
54
|
Dynamic: license-file
|
|
45
55
|
|
|
46
56
|
<img src="https://raw.githubusercontent.com/Tristanovsk/GRSdriver/master/docs/source/_static/GRS_driver.png" alt="GRSdriver_icon" width="100"/>
|
|
@@ -49,7 +59,10 @@ Dynamic: license-file
|
|
|
49
59
|
## Driver dedicated to load and prepare data for the GRS processor
|
|
50
60
|
|
|
51
61
|
[](https://pypi.org/project/GRSdriver/)
|
|
62
|
+
[](https://pypi.org/project/GRSdriver/)
|
|
63
|
+
[](https://github.com/Tristanovsk/GRSdriver/actions/workflows/tests.yml)
|
|
52
64
|
[](https://grsdriver.readthedocs.io)
|
|
65
|
+
[](https://github.com/Tristanovsk/GRSdriver/blob/master/LICENSE)
|
|
53
66
|
|
|
54
67
|
GRSdriver loads a Level-1 satellite image into a single `xarray.Dataset` ready for the
|
|
55
68
|
[GRS](https://github.com/CNES/GRSprocessor) atmospheric and sunglint correction:
|
|
@@ -59,37 +72,49 @@ GRSdriver loads a Level-1 satellite image into a single `xarray.Dataset` ready f
|
|
|
59
72
|
- Landsat 8 and 9 Collection-2 L1: reflectance, brightness temperature of the thermal bands,
|
|
60
73
|
sun/viewing angles and L1 quality flags
|
|
61
74
|
- spectral response functions and central wavelengths of each band
|
|
62
|
-
- spatial subsetting, lazy (dask) loading and low memory options for full tiles
|
|
75
|
+
- spatial subsetting, parallel reading, lazy (dask) loading and low memory options for full tiles
|
|
63
76
|
- visualization tools
|
|
64
77
|
|
|
65
78
|
Full documentation: https://grsdriver.readthedocs.io
|
|
66
79
|
|
|
80
|
+
## What's new in 2.0
|
|
81
|
+
|
|
82
|
+
- **pip-only install**: EOReader and the GDAL Python bindings are no longer needed.
|
|
83
|
+
- **~2x faster**: full Sentinel-2 tile at 20 m in ~30 s (bands read and angles computed in
|
|
84
|
+
parallel), fully lazy dask mode in ~20 s.
|
|
85
|
+
- **Fixes**: Sentinel-2 angles of processing baselines < 4.0, alignment of the Landsat
|
|
86
|
+
panchromatic band, size of the `SpatioTemp` boxes.
|
|
87
|
+
- **Upgrading from 1.x**: the drivers no longer take `**kwargs` (they were passed to EOReader) and
|
|
88
|
+
have no `.reader` attribute; a `subset` given as a sequence is a bounding box
|
|
89
|
+
`(minx, miny, maxx, maxy)` in the product CRS. See the
|
|
90
|
+
[CHANGELOG](https://github.com/Tristanovsk/GRSdriver/blob/master/CHANGELOG.md).
|
|
91
|
+
|
|
67
92
|
## Installation
|
|
68
93
|
|
|
94
|
+
Python 3.9 to 3.13:
|
|
95
|
+
|
|
69
96
|
```commandline
|
|
70
97
|
pip install GRSdriver
|
|
71
98
|
```
|
|
72
99
|
|
|
73
100
|
No separate GDAL installation is needed: rasterio and geopandas ship with their own GDAL
|
|
74
|
-
(including the JPEG2000 driver for Sentinel-2).
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
101
|
+
(including the JPEG2000 driver for Sentinel-2). Optional extras:
|
|
102
|
+
|
|
103
|
+
| Extra | For | Install |
|
|
104
|
+
|---|---|---|
|
|
105
|
+
| `cli` | `grs_driver` command line (RGB quicklooks) | `pip install "GRSdriver[cli]"` |
|
|
106
|
+
| `viz` | interactive visualization tools (`GRSdriver.visual`) | `pip install "GRSdriver[viz]"` |
|
|
107
|
+
| `tutorials` | running the tutorial notebooks | `pip install "GRSdriver[tutorials]"` |
|
|
79
108
|
|
|
80
|
-
###
|
|
109
|
+
### From the repository
|
|
110
|
+
|
|
111
|
+
To run the tutorials or work on the code:
|
|
81
112
|
```commandline
|
|
82
113
|
git clone https://github.com/Tristanovsk/GRSdriver.git
|
|
83
114
|
cd GRSdriver
|
|
84
|
-
|
|
85
|
-
conda activate grs
|
|
86
|
-
pip install -e .
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
For the interactive visualization tools and the notebooks:
|
|
90
|
-
```commandline
|
|
91
|
-
conda install -c conda-forge jupyterlab holoviews panel datashader cartopy
|
|
115
|
+
pip install -e ".[cli,viz,tutorials,dev]"
|
|
92
116
|
```
|
|
117
|
+
or with conda: `conda env create -f environment.yml && conda activate grsdriver`.
|
|
93
118
|
|
|
94
119
|
## Quick start
|
|
95
120
|
|
|
@@ -104,6 +129,12 @@ prod = l1c.prod # bands, vza, raa, sza, SRF, wl_true + metadata
|
|
|
104
129
|
# Landsat 8/9
|
|
105
130
|
l1 = LandsatDriver('LC09_L1TP_..._02_T1', resolution=30)
|
|
106
131
|
l1.load_product()
|
|
132
|
+
|
|
133
|
+
# region of interest: any geometry (GeoSeries, any CRS), here a lon/lat box
|
|
134
|
+
import geopandas as gpd
|
|
135
|
+
from shapely.geometry import box
|
|
136
|
+
roi = gpd.GeoSeries([box(5.0, 43.38, 5.25, 43.58)], crs=4326)
|
|
137
|
+
l1c = Sentinel2Driver('S2B_MSIL1C_..._.SAFE', resolution=20, subset=roi)
|
|
107
138
|
```
|
|
108
139
|
|
|
109
140
|
The bands are read by 4 threads in parallel: set `n_workers` (e.g. `n_workers=1`) to trade speed
|
|
@@ -174,6 +205,9 @@ Viewing azimuth of each band computed by GRSdriver:
|
|
|
174
205
|
|
|
175
206
|

|
|
176
207
|
|
|
177
|
-
## Changes
|
|
208
|
+
## Changes and contributing
|
|
178
209
|
|
|
179
|
-
See [CHANGELOG
|
|
210
|
+
See the [CHANGELOG](https://github.com/Tristanovsk/GRSdriver/blob/master/CHANGELOG.md). Tests run
|
|
211
|
+
with `pytest tests` (the tests that need satellite products are skipped when they are not
|
|
212
|
+
available); the [contributing guide](https://grsdriver.readthedocs.io/en/latest/contributing.html)
|
|
213
|
+
explains how to update the tutorials and make a release.
|
|
@@ -4,7 +4,10 @@
|
|
|
4
4
|
## Driver dedicated to load and prepare data for the GRS processor
|
|
5
5
|
|
|
6
6
|
[](https://pypi.org/project/GRSdriver/)
|
|
7
|
+
[](https://pypi.org/project/GRSdriver/)
|
|
8
|
+
[](https://github.com/Tristanovsk/GRSdriver/actions/workflows/tests.yml)
|
|
7
9
|
[](https://grsdriver.readthedocs.io)
|
|
10
|
+
[](https://github.com/Tristanovsk/GRSdriver/blob/master/LICENSE)
|
|
8
11
|
|
|
9
12
|
GRSdriver loads a Level-1 satellite image into a single `xarray.Dataset` ready for the
|
|
10
13
|
[GRS](https://github.com/CNES/GRSprocessor) atmospheric and sunglint correction:
|
|
@@ -14,37 +17,49 @@ GRSdriver loads a Level-1 satellite image into a single `xarray.Dataset` ready f
|
|
|
14
17
|
- Landsat 8 and 9 Collection-2 L1: reflectance, brightness temperature of the thermal bands,
|
|
15
18
|
sun/viewing angles and L1 quality flags
|
|
16
19
|
- spectral response functions and central wavelengths of each band
|
|
17
|
-
- spatial subsetting, lazy (dask) loading and low memory options for full tiles
|
|
20
|
+
- spatial subsetting, parallel reading, lazy (dask) loading and low memory options for full tiles
|
|
18
21
|
- visualization tools
|
|
19
22
|
|
|
20
23
|
Full documentation: https://grsdriver.readthedocs.io
|
|
21
24
|
|
|
25
|
+
## What's new in 2.0
|
|
26
|
+
|
|
27
|
+
- **pip-only install**: EOReader and the GDAL Python bindings are no longer needed.
|
|
28
|
+
- **~2x faster**: full Sentinel-2 tile at 20 m in ~30 s (bands read and angles computed in
|
|
29
|
+
parallel), fully lazy dask mode in ~20 s.
|
|
30
|
+
- **Fixes**: Sentinel-2 angles of processing baselines < 4.0, alignment of the Landsat
|
|
31
|
+
panchromatic band, size of the `SpatioTemp` boxes.
|
|
32
|
+
- **Upgrading from 1.x**: the drivers no longer take `**kwargs` (they were passed to EOReader) and
|
|
33
|
+
have no `.reader` attribute; a `subset` given as a sequence is a bounding box
|
|
34
|
+
`(minx, miny, maxx, maxy)` in the product CRS. See the
|
|
35
|
+
[CHANGELOG](https://github.com/Tristanovsk/GRSdriver/blob/master/CHANGELOG.md).
|
|
36
|
+
|
|
22
37
|
## Installation
|
|
23
38
|
|
|
39
|
+
Python 3.9 to 3.13:
|
|
40
|
+
|
|
24
41
|
```commandline
|
|
25
42
|
pip install GRSdriver
|
|
26
43
|
```
|
|
27
44
|
|
|
28
45
|
No separate GDAL installation is needed: rasterio and geopandas ship with their own GDAL
|
|
29
|
-
(including the JPEG2000 driver for Sentinel-2).
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
46
|
+
(including the JPEG2000 driver for Sentinel-2). Optional extras:
|
|
47
|
+
|
|
48
|
+
| Extra | For | Install |
|
|
49
|
+
|---|---|---|
|
|
50
|
+
| `cli` | `grs_driver` command line (RGB quicklooks) | `pip install "GRSdriver[cli]"` |
|
|
51
|
+
| `viz` | interactive visualization tools (`GRSdriver.visual`) | `pip install "GRSdriver[viz]"` |
|
|
52
|
+
| `tutorials` | running the tutorial notebooks | `pip install "GRSdriver[tutorials]"` |
|
|
34
53
|
|
|
35
|
-
###
|
|
54
|
+
### From the repository
|
|
55
|
+
|
|
56
|
+
To run the tutorials or work on the code:
|
|
36
57
|
```commandline
|
|
37
58
|
git clone https://github.com/Tristanovsk/GRSdriver.git
|
|
38
59
|
cd GRSdriver
|
|
39
|
-
|
|
40
|
-
conda activate grs
|
|
41
|
-
pip install -e .
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
For the interactive visualization tools and the notebooks:
|
|
45
|
-
```commandline
|
|
46
|
-
conda install -c conda-forge jupyterlab holoviews panel datashader cartopy
|
|
60
|
+
pip install -e ".[cli,viz,tutorials,dev]"
|
|
47
61
|
```
|
|
62
|
+
or with conda: `conda env create -f environment.yml && conda activate grsdriver`.
|
|
48
63
|
|
|
49
64
|
## Quick start
|
|
50
65
|
|
|
@@ -59,6 +74,12 @@ prod = l1c.prod # bands, vza, raa, sza, SRF, wl_true + metadata
|
|
|
59
74
|
# Landsat 8/9
|
|
60
75
|
l1 = LandsatDriver('LC09_L1TP_..._02_T1', resolution=30)
|
|
61
76
|
l1.load_product()
|
|
77
|
+
|
|
78
|
+
# region of interest: any geometry (GeoSeries, any CRS), here a lon/lat box
|
|
79
|
+
import geopandas as gpd
|
|
80
|
+
from shapely.geometry import box
|
|
81
|
+
roi = gpd.GeoSeries([box(5.0, 43.38, 5.25, 43.58)], crs=4326)
|
|
82
|
+
l1c = Sentinel2Driver('S2B_MSIL1C_..._.SAFE', resolution=20, subset=roi)
|
|
62
83
|
```
|
|
63
84
|
|
|
64
85
|
The bands are read by 4 threads in parallel: set `n_workers` (e.g. `n_workers=1`) to trade speed
|
|
@@ -129,6 +150,9 @@ Viewing azimuth of each band computed by GRSdriver:
|
|
|
129
150
|
|
|
130
151
|

|
|
131
152
|
|
|
132
|
-
## Changes
|
|
153
|
+
## Changes and contributing
|
|
133
154
|
|
|
134
|
-
See [CHANGELOG
|
|
155
|
+
See the [CHANGELOG](https://github.com/Tristanovsk/GRSdriver/blob/master/CHANGELOG.md). Tests run
|
|
156
|
+
with `pytest tests` (the tests that need satellite products are skipped when they are not
|
|
157
|
+
available); the [contributing guide](https://grsdriver.readthedocs.io/en/latest/contributing.html)
|
|
158
|
+
explains how to update the tutorials and make a release.
|
|
@@ -46,9 +46,14 @@ version = { attr = "GRSdriver.__version__" }
|
|
|
46
46
|
dependencies = { file = ["requirements.txt"] }
|
|
47
47
|
|
|
48
48
|
[project.optional-dependencies]
|
|
49
|
-
|
|
49
|
+
# tests and release (see the contributing guide)
|
|
50
|
+
dev = ["pytest", "build", "twine"]
|
|
50
51
|
# grs_driver command line (RGB quicklook)
|
|
51
52
|
cli = ["matplotlib", "cartopy"]
|
|
53
|
+
# interactive visualization tools (GRSdriver.visual)
|
|
54
|
+
viz = ["matplotlib", "holoviews", "datashader", "bokeh", "colorcet", "panel"]
|
|
55
|
+
# to run the tutorial notebooks
|
|
56
|
+
tutorials = ["jupyterlab", "matplotlib", "cartopy", "requests"]
|
|
52
57
|
|
|
53
58
|
[project.urls]
|
|
54
59
|
Documentation = "https://grsdriver.readthedocs.io"
|
|
@@ -94,3 +94,19 @@ def test_n_workers_identical():
|
|
|
94
94
|
l1.load_product()
|
|
95
95
|
prods.append(l1.prod)
|
|
96
96
|
np.testing.assert_array_equal(prods[0].bands.values, prods[1].bands.values)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
@pytest.mark.parametrize('resolution', [30, 60])
|
|
100
|
+
def test_dask_identical(monkeypatch, resolution):
|
|
101
|
+
'''Lazy (dask) product identical to the eager one, float32 angles in both modes.'''
|
|
102
|
+
prods = []
|
|
103
|
+
for lazy in ('0', '1'):
|
|
104
|
+
monkeypatch.setenv('GRSDRIVER_USE_DASK', lazy)
|
|
105
|
+
l1 = LandsatDriver(IMAGE, band_idx=[1, 4, 9], subset=SUBSET, resolution=resolution)
|
|
106
|
+
l1.load_product()
|
|
107
|
+
prods.append(l1.prod.compute())
|
|
108
|
+
eager, lazy = prods
|
|
109
|
+
for var in ('bands', 'sza', 'vza', 'raa'):
|
|
110
|
+
assert eager[var].dtype == lazy[var].dtype == np.float32
|
|
111
|
+
np.testing.assert_array_equal(eager[var].values, lazy[var].values)
|
|
112
|
+
np.testing.assert_array_equal(eager.flags_l1.values, lazy.flags_l1.values)
|
|
@@ -34,3 +34,32 @@ def test_clip_raster_size():
|
|
|
34
34
|
clipped = SpatioTemp.clip_raster(raster, 43.47, 5.10, 10000)
|
|
35
35
|
for coord in (clipped.x, clipped.y):
|
|
36
36
|
assert abs(float(coord.max() - coord.min()) + 100 - 10000) <= 200
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@pytest.mark.parametrize('offset', [(0, 0), (3, 5), (-4, -2), (95, 90)])
|
|
40
|
+
def test_read_on_grid_direct(tmp_path, offset):
|
|
41
|
+
'''Target grid aligned on the source grid: direct copy identical to the GDAL resampling,
|
|
42
|
+
with the fill value outside the source.'''
|
|
43
|
+
import rasterio
|
|
44
|
+
from affine import Affine
|
|
45
|
+
from rasterio.enums import Resampling
|
|
46
|
+
from GRSdriver import utils
|
|
47
|
+
src_transform = Affine(30, 0, 600000, 0, -30, 4800000)
|
|
48
|
+
data = np.arange(100 * 100, dtype=np.uint16).reshape(100, 100) + 1
|
|
49
|
+
data[10:20, 30:40] = 0
|
|
50
|
+
path = tmp_path / 'src.tif'
|
|
51
|
+
with rasterio.open(path, 'w', driver='GTiff', width=100, height=100, count=1, dtype='uint16',
|
|
52
|
+
crs='EPSG:32631', transform=src_transform, nodata=0) as dst:
|
|
53
|
+
dst.write(data, 1)
|
|
54
|
+
row, col = offset
|
|
55
|
+
transform = src_transform * Affine.translation(col, row)
|
|
56
|
+
args = (str(path), transform, 20, 30, Resampling.nearest, 0, 'float32')
|
|
57
|
+
direct = utils._read_window(*args)
|
|
58
|
+
try:
|
|
59
|
+
utils._pixel_offset, pixel_offset = (lambda *a: None), utils._pixel_offset
|
|
60
|
+
resampled = utils._read_window(*args)
|
|
61
|
+
finally:
|
|
62
|
+
utils._pixel_offset = pixel_offset
|
|
63
|
+
assert pixel_offset(src_transform, transform) == (row, col)
|
|
64
|
+
assert direct.dtype == resampled.dtype == np.float32
|
|
65
|
+
np.testing.assert_array_equal(direct, resampled)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{grsdriver-2.0.2 → grsdriver-2.0.4}/GRSdriver/rsr/data/Sentinel-2_MSI_Spectral_Responses.xlsx
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|