diff --git a/planet/order_request.py b/planet/order_request.py index 083851a8..4f5410cc 100644 --- a/planet/order_request.py +++ b/planet/order_request.py @@ -455,3 +455,61 @@ def harmonize_tool(target_sensor: str) -> dict: raise ClientError(e) return _tool('harmonize', {'target_sensor': target_sensor}) + + +def band_math_tool(b1: str, + b2: Optional[str] = None, + b3: Optional[str] = None, + b4: Optional[str] = None, + b5: Optional[str] = None, + b6: Optional[str] = None, + b7: Optional[str] = None, + b8: Optional[str] = None, + b9: Optional[str] = None, + b10: Optional[str] = None, + b11: Optional[str] = None, + b12: Optional[str] = None, + b13: Optional[str] = None, + b14: Optional[str] = None, + b15: Optional[str] = None, + pixel_type: str = specs.BAND_MATH_PIXEL_TYPE_DEFAULT): + '''Specify an Orders API band math tool. + + The parameters of the bandmath tool define how each output band in the + derivative product should be produced, referencing the product inputs’ + original bands. Band math expressions may not reference neighboring pixels, + as non-local operations are not supported. The tool can calculate up to 15 + bands for an item. Input band parameters may not be skipped. For example, + if the b4 parameter is provided, then b1, b2, and b3 parameters are also + required. + + For each band expression, the bandmath tool supports normal arithmetic + operations and simple math operators offered in the Python numpy package. + (For a list of supported mathematical functions, see + [Bandmath supported numpy math routines](https://developers.planet.com/apis/orders/bandmath-numpy-routines/)). + + One bandmath imagery output file is produced for each product bundle, with + output bands derived from the band math expressions. nodata pixels are + processed with the band math equation. These files have “_bandmath” + appended to their file names. + + The tool passes through UDM, RPC, and XML files, and does not update values + in these files. + + Parameters: + b1-b15: An expression defining how the output band should be computed. + pixel_type: A value indicating what the output pixel type should be. + + Raises: + planet.exceptions.ClientError: If pixel_type is not valid. + ''' # noqa + try: + pixel_type = specs.get_match(pixel_type, + specs.BAND_MATH_PIXEL_TYPE, + 'pixel_type') + except specs.SpecificationException as e: + raise ClientError(e) + + # e.g. {"b1": "b1", "b2":"arctan(b1)"} if b1 and b2 are specified + parameters = dict((k, v) for k, v in locals().items() if v) + return _tool('bandmath', parameters) diff --git a/planet/specs.py b/planet/specs.py index e0d3e3b6..886c8aeb 100644 --- a/planet/specs.py +++ b/planet/specs.py @@ -20,7 +20,7 @@ PRODUCT_BUNDLE_SPEC_NAME = 'orders_product_bundle_2023-02-24.json' SUPPORTED_TOOLS = [ - 'band_math', + 'bandmath', 'clip', 'composite', 'coregister', @@ -34,6 +34,8 @@ SUPPORTED_ARCHIVE_TYPES = ['zip'] SUPPORTED_FILE_FORMATS = ['COG', 'PL_NITF'] HARMONIZE_TOOL_TARGET_SENSORS = ('Sentinel-2', 'PS2') +BAND_MATH_PIXEL_TYPE = ('Auto', '8U', '16U', '16S', '32R') +BAND_MATH_PIXEL_TYPE_DEFAULT = 'Auto' LOGGER = logging.getLogger(__name__) diff --git a/planet/subscription_request.py b/planet/subscription_request.py index 26b761e0..9bc1e341 100644 --- a/planet/subscription_request.py +++ b/planet/subscription_request.py @@ -18,9 +18,6 @@ from . import geojson, specs from .exceptions import ClientError -BAND_MATH_PIXEL_TYPE = ('Auto', '8U', '16U', '16S', '32R') -BAND_MATH_PIXEL_TYPE_DEFAULT = 'Auto' - NOTIFICATIONS_TOPICS = ('delivery.success', 'delivery.match', 'delivery.failed', @@ -296,7 +293,7 @@ def band_math_tool(b1: str, b13: Optional[str] = None, b14: Optional[str] = None, b15: Optional[str] = None, - pixel_type: str = BAND_MATH_PIXEL_TYPE_DEFAULT): + pixel_type: str = specs.BAND_MATH_PIXEL_TYPE_DEFAULT): '''Specify a subscriptions API band math tool. The parameters of the bandmath tool define how each output band in the @@ -329,7 +326,7 @@ def band_math_tool(b1: str, ''' # noqa try: pixel_type = specs.get_match(pixel_type, - BAND_MATH_PIXEL_TYPE, + specs.BAND_MATH_PIXEL_TYPE, 'pixel_type') except specs.SpecificationException as e: raise ClientError(e) diff --git a/tests/unit/test_order_request.py b/tests/unit/test_order_request.py index 63ec9d0b..48f3be00 100644 --- a/tests/unit/test_order_request.py +++ b/tests/unit/test_order_request.py @@ -51,7 +51,7 @@ def test_build_request(): 'webhook_per_order': True } order_type = 'partial' - tool = {'band_math': 'jsonstring'} + tool = {'bandmath': 'jsonstring'} stac_json = {'stac': {}} request = order_request.build_request('test_name', [product], @@ -215,8 +215,8 @@ def test_google_earth_engine(): def test__tool(): - test_tool = order_request._tool('band_math', 'jsonstring') - assert test_tool == {'band_math': 'jsonstring'} + test_tool = order_request._tool('bandmath', 'jsonstring') + assert test_tool == {'bandmath': 'jsonstring'} with pytest.raises(specs.SpecificationException): _ = order_request._tool('notsupported', 'jsonstring') @@ -272,3 +272,23 @@ def test_harmonization_tool_success(): def test_harmonization_tool_invalid_target_sensor(): with pytest.raises(exceptions.ClientError): order_request.harmonize_tool('invalid') + + +def test_band_math_tool_success(): + res = order_request.band_math_tool(b1='b1', b2='arctan(b1)') + + expected = { + "bandmath": { + "b1": "b1", + "b2": "arctan(b1)", + "pixel_type": specs.BAND_MATH_PIXEL_TYPE_DEFAULT + } + } + assert res == expected + + +def test_band_math_tool_invalid_pixel_type(): + with pytest.raises(exceptions.ClientError): + order_request.band_math_tool(b1='b1', + b2='arctan(b1)', + pixel_type="invalid") diff --git a/tests/unit/test_subscription_request.py b/tests/unit/test_subscription_request.py index 51e75903..dfc2ac19 100644 --- a/tests/unit/test_subscription_request.py +++ b/tests/unit/test_subscription_request.py @@ -16,7 +16,7 @@ import pytest -from planet import exceptions, subscription_request +from planet import exceptions, subscription_request, specs LOGGER = logging.getLogger(__name__) @@ -191,7 +191,7 @@ def test_band_math_tool_success(): "parameters": { "b1": "b1", "b2": "arctan(b1)", - "pixel_type": subscription_request.BAND_MATH_PIXEL_TYPE_DEFAULT + "pixel_type": specs.BAND_MATH_PIXEL_TYPE_DEFAULT } } assert res == expected