Skip to content

Commit 27479d6

Browse files
committed
- add support for COVERAGE LIST VALUES, for explicit enumeration of cell values over a domain (Coverage.list_values method)
- add examples for convolution and switch in the README - better validation of multiple axis specifications in a subset
1 parent 34b3812 commit 27479d6

File tree

5 files changed

+256
-19
lines changed

5 files changed

+256
-19
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,6 @@ __pycache__
88
# documentation
99
autoapi
1010
_build
11+
12+
# testing
13+
test.py

README.md

Lines changed: 88 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from model import MultiBand
2+
13
# Overview
24

35
The [OGC Web Coverage Processing Service (WCPS) standard](https://www.ogc.org/standards/wcps)
@@ -233,8 +235,9 @@ Other reduce methods include `sum()`, `max()`, `min()`, `all()`, `some()`.
233235

234236
## Timeseries Aggregation
235237

236-
A more advanced expression is the *general condenser* (aggregation)
237-
operation. The example calculates a map with maximum cell values across all time slices
238+
A more advanced expression is the
239+
[general condenser](https://rasdaman.github.io/wcps-python-client/autoapi/wcps/model/index.html#wcps.model.Condense)
240+
(aggregation) operation. The example calculates a map with maximum cell values across all time slices
238241
from a 3D datacube between "2015-01-01" and "2015-07-01", considering only the
239242
time slices with an average greater than 20:
240243

@@ -260,7 +263,9 @@ service.download(query, 'max_map.png')
260263
```
261264

262265
How about calculating the average of each time slice between two dates?
263-
This can be done with a *coverage constructor*, which will iterate over all dates
266+
This can be done with a
267+
[coverage constructor](https://rasdaman.github.io/wcps-python-client/autoapi/wcps/model/index.html#wcps.model.Condense),
268+
which will iterate over all dates
264269
between the two given dates, resulting in a 1D array of average NDVI values;
265270
notice that the slicing on the time axis ansi is done with the "iterator" variable `ansi_iter`
266271
like in the previous example. The 1D array is encoded as JSON in the end.
@@ -328,6 +333,86 @@ plt.ylabel('Average')
328333
plt.show()
329334
```
330335

336+
## Convolution
337+
338+
The [coverage constructor](https://rasdaman.github.io/wcps-python-client/autoapi/wcps/model/index.html#wcps.model.Condense)
339+
supports also enumerating the cell values in place as a list of numbers.
340+
This allows to specify small arrays such as
341+
[convolution kernels](https://en.wikipedia.org/wiki/Kernel_(image_processing)),
342+
enabling more advanced image processing operation. The example below uses a
343+
[Sobel operator](https://en.wikipedia.org/wiki/Sobel_operator)
344+
to perform edge detection on an image on the server, before downloading the result.
345+
346+
```python
347+
from wcps.service import Service
348+
from wcps.model import Datacube, Coverage, Condense, \
349+
AxisIter, CondenseOp
350+
351+
# kernels
352+
x = AxisIter('$x', 'x').interval(-1, 1)
353+
y = AxisIter('$y', 'y').interval(-1, 1)
354+
kernel1 = (Coverage('kernel1').over([x, y])
355+
.value_list([1, 0, -1, 2, 0, -2, 1, 0, -1]))
356+
kernel2 = (Coverage('kernel2').over([x, y])
357+
.value_list([1, 2, 1, 0, 0, 0, -1, -2, -1]))
358+
359+
# coverage axis iterators
360+
cov = Datacube("NIR")
361+
subset = [( "i", 10, 500 ), ( "j", 10, 500 )]
362+
cx = AxisIter('$px', 'i').of_grid_axis(cov[subset])
363+
cy = AxisIter('$py', 'j').of_grid_axis(cov[subset])
364+
365+
# kernel axis iterators
366+
kx = AxisIter('$kx', 'x').interval(-1, 1)
367+
ky = AxisIter('$ky', 'y').interval(-1, 1)
368+
369+
gx = (Coverage('Gx').over([cx, cy])
370+
.values(Condense(CondenseOp.PLUS).over([kx, ky])
371+
.using(kernel1["x": kx.ref(), "y": ky.ref()] *
372+
cov.green["i": cx.ref() + kx.ref(),
373+
"j": cy.ref() + ky.ref()])
374+
)
375+
).pow(2.0)
376+
377+
gy = (Coverage('Gy').over([cx, cy])
378+
.values(Condense(CondenseOp.PLUS).over([kx, ky])
379+
.using(kernel2["x": kx.ref(), "y": ky.ref()] *
380+
cov.green["i": cx.ref() + kx.ref(),
381+
"j": cy.ref() + ky.ref()])
382+
)
383+
).pow(2.0)
384+
385+
query = (gx + gy).sqrt().encode("image/jpeg")
386+
387+
service = Service("https://ows.rasdaman.org/rasdaman/ows")
388+
service.download(query, 'convolution.png')
389+
```
390+
391+
## Case Distinction
392+
393+
Conditional evaluation is possible with
394+
[Switch](https://rasdaman.github.io/wcps-python-client/autoapi/wcps/model/index.html#wcps.model.Switch):
395+
396+
```python
397+
from wcps.service import Service
398+
from wcps.model import Datacube, Switch, rgb
399+
400+
cov = Datacube("AvgLandTemp")["ansi": "2014-07",
401+
"Lat": 35: 75,
402+
"Long": -20: 40]
403+
switch = (Switch()
404+
.case(cov == 99999).then(rgb(255, 255, 255))
405+
.case(cov < 18).then(rgb(0, 0, 255))
406+
.case(cov < 23).then(rgb(255, 255, 0))
407+
.case(cov < 30).then(rgb(255, 140, 0))
408+
.default(rgb(255, 0, 0)))
409+
410+
query = switch.encode("image/png")
411+
412+
service = Service("https://ows.rasdaman.org/rasdaman/ows")
413+
service.show(query)
414+
```
415+
331416
## User-Defined Functions (UDF)
332417

333418
UDFs can be executed with the

tests/test_model.py

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,10 @@ def test_condense():
161161
"using ($cov1[time($pt)] * $px))")
162162

163163

164-
def test_coverage():
164+
# -------------------------------------------------------------------------------------
165+
# Coverage
166+
167+
def test_coverage_of_geo_axis():
165168
plat_var = AxisIter('$pLat', 'Lat').of_geo_axis(cov1['Lat', -30, -28.5])
166169
plon_var = AxisIter('$pLon', 'Lon').of_geo_axis(cov1['Lon', 111.975, 113.475])
167170
cov_expr = (Coverage('targetCoverage')
@@ -175,25 +178,77 @@ def test_coverage():
175178
"values $cov1[Lat($pLat), Lon($pLon)])")
176179

177180

181+
def test_coverage_with_interval():
182+
time_var = AxisIter('$pt', 'time').interval(0, 10)
183+
cov_expr = (Coverage('intervalCoverage')
184+
.over(time_var)
185+
.values(cov1['time', time_var.ref()]))
186+
187+
assert str(cov_expr) == (
188+
"for $cov1 in (cov1)\nreturn\n "
189+
"(coverage intervalCoverage over $pt time(0:10) values $cov1[time($pt)])"
190+
)
191+
192+
193+
def test_coverage_with_interval_and_params():
194+
time_var = AxisIter('$pt', 'time').interval(0, 10)
195+
196+
# Create a Coverage expression with additional parameters
197+
cov_expr = ((Coverage('intervalCoverage')
198+
.over(time_var)
199+
.values(cov1['time', time_var.ref()]))
200+
.encode('GTiff')
201+
.params('{ "configOptions": { "GDAL_CACHEMAX": "64" } }'))
202+
expected_query = (
203+
'for $cov1 in (cov1)\nreturn\n '
204+
'encode((coverage intervalCoverage over $pt time(0:10) values $cov1[time($pt)]), '
205+
'"GTiff", "{ \\"configOptions\\": { \\"GDAL_CACHEMAX\\": \\"64\\" } }")'
206+
)
207+
208+
assert str(cov_expr) == expected_query
209+
210+
211+
def test_coverage_no_axes():
212+
with pytest.raises(WCPSClientException):
213+
str(Coverage('noAxesCoverage').values(cov1))
214+
215+
216+
def test_coverage_no_values():
217+
lat_var = AxisIter('$pLat', 'Lat').of_geo_axis(cov1['Lat', -30, -28.5])
218+
with pytest.raises(WCPSClientException):
219+
str(Coverage('noValuesCoverage').over(lat_var))
220+
221+
222+
def test_coverage_with_nested_condense():
223+
224+
225+
226+
# -------------------------------------------------------------------------------------
227+
# Encode
228+
178229
def test_encode():
179230
assert str(Encode(cov1, "PNG")) == 'for $cov1 in (cov1)\nreturn\n encode($cov1, "PNG")'
180231
assert str(Encode(cov1, "PNG", "params")) == \
181232
'for $cov1 in (cov1)\nreturn\n encode($cov1, "PNG", "params")'
182233

234+
183235
def test_encode_to_png():
184236
encode_expr = Encode(cov1, "PNG")
185237
expected_query = "for $cov1 in (cov1)\nreturn\n encode($cov1, \"PNG\")"
186238
assert str(encode_expr) == expected_query
187239

240+
188241
def test_encode_with_params():
189242
encode_expr = Encode(cov1, "PNG").params('{"compression":"lzw"}')
190243
expected_query = 'for $cov1 in (cov1)\nreturn\n encode($cov1, "PNG", "{\\"compression\\":\\"lzw\\"}")'
191244
assert str(encode_expr) == expected_query
192245

246+
193247
def test_encode_no_format():
194248
with pytest.raises(WCPSClientException):
195249
str(Encode(cov1))
196250

251+
197252
# -------------------------------------------------------------------------------------
198253
# Switch
199254

wcps/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,4 @@
55
"""
66
__title__ = 'wcps'
77
__author__ = 'rasdaman team'
8-
__version__ = "0.4.1"
8+
__version__ = "0.4.2"

0 commit comments

Comments
 (0)