Skip to content

Commit c7f5912

Browse files
authored
Release 3.10.1, Merge pull request #518 from sentinel-hub/develop
Release 3.10.1
2 parents 500cf5c + e4fb34f commit c7f5912

21 files changed

+7499
-73
lines changed

.pre-commit-config.yaml

+3-3
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,18 @@ repos:
1313
- id: debug-statements
1414

1515
- repo: https://github.com/psf/black
16-
rev: 23.11.0
16+
rev: 23.12.1
1717
hooks:
1818
- id: black
1919
language_version: python3
2020

2121
- repo: https://github.com/charliermarsh/ruff-pre-commit
22-
rev: "v0.1.5"
22+
rev: "v0.1.11"
2323
hooks:
2424
- id: ruff
2525

2626
- repo: https://github.com/nbQA-dev/nbQA
27-
rev: 1.7.0
27+
rev: 1.7.1
2828
hooks:
2929
- id: nbqa-black
3030
- id: nbqa-ruff

CHANGELOG.MD

+10-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
1-
## [Version 3.10.0] - 2023-12-07
1+
## [Version 3.10.1] - 2024-01-10
2+
3+
- Improved documentation for Copernicus Data Space Ecosystem.
4+
- Restrict numpy version to <2 in anticipation of numpy 2.0 release.
5+
6+
7+
## [Version 3.10.0] - 2023-12-08
28

39
- Adjust session caching to mirror changes to the core services. Older version might no longer correctly cache sessions.
10+
- Switch default auth endpoint to the new address.
11+
412

513
## [Version 3.9.5] - 2023-12-07
614

@@ -12,6 +20,7 @@
1220
- Fixed a problem with `dataclasses_json 0.6.2` that broke BYOC functionalities
1321
- Removed AWS examples from the docs since the functionality is no longer maintained.
1422

23+
1524
## [Version 3.9.3] - 2023-11-03
1625

1726
- `SHConfig` now correctly initializes a default profile in the file even if the first initialization call is done with a custom profile.

docs/source/tutorials.rst docs/source/advanced.rst

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
Advanced tutorials
2-
==================
1+
Sentinel Hub advanced tutorials
2+
===============================
33

44
.. toctree::
55
:maxdepth: 4

docs/source/cdse.rst

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
.. _cdse_tutorials:
2+
Copernicus Data Space Ecosystem examples
3+
========================================
4+
5+
.. toctree::
6+
:maxdepth: 4
7+
8+
examples/process_request_cdse.ipynb
9+
examples/data_search_cdse.ipynb
10+
examples/ogc_request_cdse.ipynb
11+
examples/large_area_utilities_cdse.ipynb
12+
examples/statistical_request_cdse.ipynb

docs/source/configure.rst

+29
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,32 @@ Batch, BYOC, and other APIs). There is "OAuth clients" frame where we can create
9696
For detailed instructions on how to obtain credentials, you can see the `Sentinel Hub webinar`_.
9797

9898

99+
Copernicus Data Space Ecosystem Configuration
100+
*********************************************
101+
102+
103+
For Copernicus Data Space Ecosystem users, please follow the `Sentinel Hub Services Authentication instructions`_ to
104+
register an OAuth client using the `Sentinel Hub Services Dashboard`_.
105+
106+
With the registered OAuth client, a valid Copernicus Data Space Ecosystem configuration should be configured as below:
107+
108+
.. code-block:: python
109+
110+
from sentinelhub import SHConfig
111+
112+
config = SHConfig()
113+
config.sh_client_id = 'oauth-client-id'
114+
config.sh_client_secret = 'oauth-client-secret'
115+
config.sh_base_url = 'https://sh.dataspace.copernicus.eu'
116+
config.sh_token_url = 'https://identity.dataspace.copernicus.eu/auth/realms/CDSE/protocol/openid-connect/token'
117+
118+
119+
.. admonition:: Supported Data Collections
120+
121+
With the Copernicus Data Space Ecosystem Configuration, please find the available data collections on
122+
`Copernicus Data Space Ecosystem indexed in Sentinel Hub`_. See the :ref:`cdse_tutorials` on how to configure them for direct use in `sentinelhub-py`.
123+
124+
99125
Other configuration options
100126
***************************
101127

@@ -110,3 +136,6 @@ $ sentinelhub.config --help
110136
.. _`Sentinel Hub services`: https://www.sentinel-hub.com/develop/documentation/api/ogc_api/
111137
.. _`Sentinel Hub webinar`: https://www.youtube.com/watch?v=CBIlTOl2po4&t=1760s
112138
.. _`Sentinel Hub public repository`: https://roda.sentinel-hub.com/sentinel-s2-l1c/
139+
.. _`Sentinel Hub Services Authentication instructions`: https://documentation.dataspace.copernicus.eu/APIs/SentinelHub/Overview/Authentication.html
140+
.. _`Sentinel Hub Services Dashboard`: https://shapps.dataspace.copernicus.eu/dashboard/#/
141+
.. _`Copernicus Data Space Ecosystem indexed in Sentinel Hub`: https://documentation.dataspace.copernicus.eu/APIs/SentinelHub/Data.html

docs/source/examples.rst

+6-13
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,9 @@
1-
Basic examples
2-
==============
1+
Examples
2+
========
33

44
.. toctree::
5-
:maxdepth: 4
5+
:maxdepth: 1
66

7-
examples/process_request.ipynb
8-
examples/data_collections.ipynb
9-
examples/data_search.ipynb
10-
examples/ogc_request.ipynb
11-
examples/large_area_utilities.ipynb
12-
examples/batch_processing.ipynb
13-
examples/batch_statistical.ipynb
14-
examples/byoc_request.ipynb
15-
examples/statistical_request.ipynb
16-
examples/fis_request.ipynb
7+
sh
8+
cdse
9+
advanced

docs/source/index.rst

-1
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,4 @@ Documentation of `sentinelhub` Python package
1111
configure
1212
logging
1313
examples
14-
tutorials
1514
reference/sentinelhub

docs/source/sh.rst

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
Sentinel Hub examples
2+
=====================
3+
4+
.. toctree::
5+
:maxdepth: 4
6+
7+
examples/process_request.ipynb
8+
examples/data_collections.ipynb
9+
examples/data_search.ipynb
10+
examples/ogc_request.ipynb
11+
examples/large_area_utilities.ipynb
12+
examples/batch_processing.ipynb
13+
examples/batch_statistical.ipynb
14+
examples/byoc_request.ipynb
15+
examples/statistical_request.ipynb
16+
examples/fis_request.ipynb
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
//VERSION=3
2+
3+
// Calculate number of bands needed for all intervals
4+
// Initialize dates and interval
5+
// Beware: in JS months are 0 indexed
6+
var start_date = new Date(2020, 6, 1, 0, 0, 0);
7+
var end_date = new Date(2020, 6, 30, 0, 0, 0);
8+
var sampled_dates = sample_timestamps(start_date, end_date, 7, 'day').map(d => withoutTime(d));
9+
var nb_bands = sampled_dates.length;
10+
var n_valid = 0;
11+
var n_all = 0;
12+
13+
function interval_search(x, arr) {
14+
let start_idx = 0, end_idx = arr.length - 2;
15+
16+
// Iterate while start not meets end
17+
while (start_idx <= end_idx) {
18+
// Find the mid index
19+
let mid_idx = (start_idx + end_idx) >> 1;
20+
21+
// If element is present at mid, return True
22+
if (arr[mid_idx] <= x && x < arr[mid_idx + 1]) {
23+
return mid_idx;
24+
}
25+
// Else look in left or right half accordingly
26+
else if (arr[mid_idx + 1] <= x) start_idx = mid_idx + 1;
27+
else end_idx = mid_idx - 1;
28+
}
29+
if (x == arr[arr.length-1]){
30+
return arr.length-2;
31+
}
32+
return undefined;
33+
}
34+
35+
function linearInterpolation(x, x0, y0, x1, y1, no_data_value=NaN) {
36+
if (x < x0 || x > x1) {
37+
return no_data_value;
38+
}
39+
var a = (y1 - y0) / (x1 - x0);
40+
var b = -a * x0 + y0;
41+
return a * x + b;
42+
}
43+
44+
function lininterp(x_arr, xp_arr, fp_arr, no_data_value=NaN) {
45+
results = [];
46+
data_mask = [];
47+
xp_arr_idx = 0;
48+
for (var i=0; i<x_arr.length; i++) {
49+
var x = x_arr[i];
50+
n_all+=1;
51+
interval = interval_search(x, xp_arr);
52+
if (interval === undefined) {
53+
data_mask.push(0);
54+
results.push(no_data_value);
55+
continue;
56+
}
57+
data_mask.push(1);
58+
n_valid+=1;
59+
results.push(
60+
linearInterpolation(
61+
x,
62+
xp_arr[interval],
63+
fp_arr[interval],
64+
xp_arr[interval+1],
65+
fp_arr[interval+1],
66+
no_data_value
67+
)
68+
);
69+
}
70+
return [results, data_mask];
71+
}
72+
73+
function interpolated_index(index_a, index_b) {
74+
// Calculates the index for all bands in array
75+
var index_data = [];
76+
for (var i = 0; i < index_a.length; i++){
77+
// UINT index returned
78+
let ind = (index_a[i] - index_b[i]) / (index_a[i] + index_b[i]);
79+
index_data.push(ind * 10000 + 10000);
80+
}
81+
return index_data
82+
}
83+
84+
function increase(original_date, period, period_unit) {
85+
date = new Date(original_date)
86+
switch (period_unit) {
87+
case 'millisecond':
88+
return new Date(date.setMilliseconds(date.getMilliseconds()+period));
89+
case 'second':
90+
return new Date(date.setSeconds(date.getSeconds()+period));
91+
case 'minute':
92+
return new Date(date.setMinutes(date.getMinutes()+period));
93+
case 'hour':
94+
return new Date(date.setHours(date.getHours()+period));
95+
case 'day':
96+
return new Date(date.setDate(date.getDate()+period));
97+
case 'month':
98+
return new Date(date.setMonth(date.getMonth()+period));
99+
default:
100+
return undefined
101+
}
102+
}
103+
104+
function sample_timestamps(start, end, period, period_unit) {
105+
var cDate = new Date(start);
106+
var sampled_dates = []
107+
while (cDate < end) {
108+
sampled_dates.push(cDate);
109+
cDate = increase(cDate, period, period_unit);
110+
}
111+
return sampled_dates;
112+
}
113+
114+
function is_valid(smp) {
115+
// Check if the sample is valid (i.e. contains no clouds or snow)
116+
let scl = smp.SCL;
117+
let dm = smp.dataMask;
118+
const clouds = [0, 3, 7, 8, 9, 10];
119+
120+
if (clouds.includes(scl)) {
121+
return false;
122+
}
123+
if (dm !=1 ) {
124+
return false;
125+
}
126+
return true;
127+
}
128+
129+
function withoutTime(intime) {
130+
// Return date without time
131+
intime.setHours(0, 0, 0, 0);
132+
return intime;
133+
}
134+
135+
// Sentinel Hub functions
136+
function setup() {
137+
// Setup input/output parameters
138+
return {
139+
input: [{
140+
bands: ["B04", "B08", "SCL", "dataMask"],
141+
units: "DN"
142+
}],
143+
output: [
144+
{id: "NDVI", bands: nb_bands, sampleType: SampleType.UINT16},
145+
{id: "data_mask", bands: nb_bands, sampleType: SampleType.UINT8}
146+
],
147+
mosaicking: "ORBIT"
148+
}
149+
}
150+
151+
// Evaluate pixels in the bands
152+
function evaluatePixel(samples, scenes) {
153+
154+
// Initialise arrays
155+
var valid_samples = {'B04':[], 'B08':[]};
156+
157+
var valid_dates = []
158+
// Loop over samples.
159+
for (var i = samples.length-1; i >= 0; i--){
160+
if (is_valid(samples[i])) {
161+
valid_dates.push(withoutTime(new Date(scenes[i].date)));
162+
valid_samples['B04'].push(samples[i].B04);
163+
valid_samples['B08'].push(samples[i].B08);
164+
}
165+
}
166+
167+
// Calculate indices and return optimised for UINT16 format (will need unpacking)
168+
var ndvi = interpolated_index(valid_samples['B08'], valid_samples['B04'])
169+
170+
var [ndvi_interpolated, dm] = lininterp(sampled_dates, valid_dates, ndvi, 0);
171+
172+
// Return all arrays
173+
return {
174+
NDVI: ndvi,
175+
data_mask: dm
176+
}
177+
}
+66
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
//VERSION=3
2+
3+
function setup() {
4+
return {
5+
input: [{
6+
bands: ["B01", "B02", "B03", "B04", "B05", "B06", "B07", "B08", "B8A", "B09", "B11", "B12", "SCL", "dataMask"],
7+
units: "DN"
8+
}],
9+
output: [
10+
{
11+
id: "bands",
12+
bands: ["B01", "B02", "B03", "B04", "B05", "B06", "B07", "B08", "B8A", "B09", "B11", "B12"],
13+
sampleType: "UINT16"
14+
},
15+
{
16+
id: "masks",
17+
bands: ["SCL"],
18+
sampleType: "UINT16"
19+
},
20+
{
21+
id: "indices",
22+
bands: ["NDVI", "NDVI_RE1", "NBSI"],
23+
sampleType: "UINT16"
24+
},
25+
{
26+
id: "dataMask",
27+
bands: 1
28+
}]
29+
}
30+
}
31+
32+
function evaluatePixel(samples) {
33+
// Normalised Difference Vegetation Index and variation
34+
let NDVI = index(samples.B08, samples.B04);
35+
let NDVI_RE1 = index(samples.B08, samples.B05);
36+
37+
// Bare Soil Index
38+
let NBSI = index((samples.B11 + samples.B04), (samples.B08 + samples.B02));
39+
40+
// masking cloudy pixels
41+
const clouds = [0, 3, 7, 8, 9, 10];
42+
let combinedMask = samples.dataMask
43+
if (clouds.includes(samples.SCL)) {
44+
combinedMask = 0;
45+
}
46+
47+
const f = 5000;
48+
return {
49+
bands: [samples.B01, samples.B02, samples.B03, samples.B04, samples.B05, samples.B06,
50+
samples.B07, samples.B08, samples.B8A, samples.B09, samples.B11, samples.B12],
51+
masks: [samples.CLM],
52+
indices: [toUINT(NDVI, f), toUINT(NDVI_RE1, f), toUINT(NBSI, f)],
53+
dataMask: [combinedMask]
54+
};
55+
}
56+
57+
function toUINT(product, constant){
58+
// Clamp the output to [-1, 10] and convert it to a UNIT16
59+
// value that can be converted back to float later.
60+
if (product < -1) {
61+
product = -1;
62+
} else if (product > 10) {
63+
product = 10;
64+
}
65+
return Math.round(product * constant) + constant;
66+
}

0 commit comments

Comments
 (0)