Tasks

The Tasks API provides scalable compute capabilities to parallelize your computations. It works by pickling your Python code and executing the code on nodes hosted by Descartes Labs in our cloud infrastructure. These nodes are able to access imagery at extremely high rates of throughput which, paired with horizontal scaling, allow you to execute computations over nearly any spatiotemporal scale.

Basic Example

# import relevant methods from Tasks API
from descarteslabs.client.services.tasks import Tasks, as_completed

# define function to scale out
def hello(i):
   import geopandas
   print(geopandas)
   return "hello {}".format(i)

# create task group
client = Tasks()
async_func = client.create_function(
hello,
name='my-task-hello',
image="us.gcr.io/dl-ci-cd/images/tasks/public/py3.5/default:v2018.10.23-1-ga3b35ab8",
)

# submit a task to the task group
task = async_func(5)

# print the task result and logs
print task.result
print task.log

This example illustrates the basics usage of the Tasks API. Here, we define a function called hello which imports geopandas, prints out information about the package, and returns the string hello <argument>.

A task group is generated using the create_function method which specifies the function scale (hello), gives the task group a name, and specifies a Docker image that defines the environment in which the code will be executed.

In the next line, we call the async function to submit a single task. This submits the task to the task group created by the create_function call, and the reference to the task is stored in the ‘task’ variable. This also triggers instances to spin up on the backend to execute the task. Instance management is handled in the background. Instances are created or destroyed as needed to match the compute resources required by the job.

A few important features of the Tasks API are highlighted by this example:

  • You can pass any JSON-serializable argument to a task, e.g. arguments with type str, dict, list, None, or any numeric data type.
  • You can return a value from a task just like you can from a function executed locally. The return value is accessed via task.result. While arbitrary return values are supported, it is highly recommended that these are JSON-serializable or numpy arrays.
  • You can import nonstandard Python packages within your async function. Any packages that your function uses that are outside of the standard Python library need to be imported within the function itself. The function is the only segment of the code being serialized and sent to the node, so all variables and packages need to be sent along with it.
  • Any packages that your function imports also need to be installed on the Docker image used to define the execution environment. The default Docker image contains a number of common dependencies for geospatial analysis. Currently, you are not currently able to generate your own image or install additional packages.
  • You can access any logging or debugging information, including print statements executed inside your function, through the logs stored in task.log.

The next example illustrates the more typical use case of submitting multiple tasks.

Multiple Tasks Example

from descarteslabs.client.services.tasks import Tasks, as_completed
import numpy as np

# define the function to scale out
def generate_random_image(num_bands):

    # import numpy within the async function
    import numpy as np

    image_shape = (100, 100)
    image = np.random.rand(num_bands, *image_shape)


# create the task group
client = Tasks()
async_func = client.create_function(
generate_random_image,
name='my-task-random-image',
image="us.gcr.io/dl-ci-cd/images/tasks/public/py3.5/default:v2018.10.23-1-ga3b35ab8",
)

# submit 20 tasks to the task group
tasks = async_func.map(range(20))

# print the shape of the image array returned by each task

for task in as_completed(tasks):
     if task.is_success:
        print(task.result.shape)
     else:
        print(task.exception)
        print(task.log)

Here, we are defining a new function that generates a random image using numpy where the user passes the number of bands to the n_bands parameter.

This example highlights a few additional features of the Tasks API:

  • To submit tasks to the task group, we are using the map method to submit a task for each of the elements in the list. This is typically the most efficient way to submit tasks to a task group, particularly if the number of tasks is large. You are also able to submit tasks one at a time, e.g. within in a for-loop.
  • We use the as_completed method to retrieve the task results for each task as it is completed. Within this loop, we also catch exceptions and print the logs of any failed task.

GPU-enabled Task Example

In this example, we use GPU-enabled tasks to do TensorFlow matrix multiplication on a GPU. We do this by defining a TensorFlow task that explicitly places operations on the GPU, and we request a GPU when creating the task group. We use a GPU-enabled container image for this task.

from descarteslabs.client.services.tasks import Tasks
tasks_client = Tasks()


# We define our TensorFlow task.
def gpu_tf_ex(n):
    import tensorflow as tf

    # We explicitly tell TensorFlow to execute the subsequent operations on the GPU.
    with tf.device('/device:GPU:0'):
        a = tf.constant(list(range(1, 2 * (n + 1))), dtype=tf.float32, shape=[n - 1, n + 1], name='a')
        b = tf.constant(list(range(1, 2 * (n + 1))), dtype=tf.float32, shape=[n + 1, n - 1], name='b')
        c = tf.matmul(a, b)

    result = None

    # By setting allow_soft_placement to False, this task will fail if it can't
    # execute the above computation on a GPU.
    with tf.Session(config=tf.ConfigProto(allow_soft_placement=False)) as sess:
        result = sess.run(c)
    return result


# We create our task function, which creates a new task group in the process.  Note that we use
# a public GPU-enabled image that Descartes provides: py3.5-gpu.
async_function = tasks_client.create_function(
    gpu_tf_ex,
    image='us.gcr.io/dl-ci-cd/images/tasks/public/py3.5-gpu/default:v2018.11.09-8-g635b331c',
    name='gpu_tf_ex',
    # You can request GPUs exactly as you would request CPUs.
    gpus=1
)

# We launch a task, wait for it to complete, and print the result.  This task will only
# succeed if TensorFlow was able to execute the matrix multiplication on the GPU.
task = async_function(3)
async_function.wait_for_completion(show_progress=True)
print('Output of function:')
print(task.result)

When creating a GPU-enabled task group, add the gpus=1 keyword argument to your tasks_client.create_function, tasks_client.create_or_get_function, or tasks_client.new_group function call. The only other thing you’ll need to run GPU-enabled tasks is a container image that contains the NVIDIA CUDA library and GPU-supporting libraries for whatever computation you wish to do (e.g. the tensorflow-gpu library for TensorFlow with GPU support.) We provide several public GPU-oriented container images for Python 2.7, 3.5, and 3.6; see the GPU-enabled images in the Current Images <current-images> section.

This task will only succeed (and return a result) if the TensorFlow matrix multiplication is able to be placed on a GPU. We only do this for demonstration purposes, however. If you’re using a machine learning framework with a backend that does automatic device placement for operators (e.g. Keras with a TensorFlow backend), you won’t have to explicitly place operations on the GPU; the backend will automatically do that for you. If you’ve already defined a task function that does Keras model training or inference, you shouldn’t have to changed that code to take advantage of GPU training/inference within our tasks service.

Choosing Your Environment

The execution environment for your function in the cloud is defined by the docker image you pick when creating the function. The below images are available covering typical use cases. If none of the provided images suits your needs, contact support@descarteslabs.com about customizing an image.

Match your local Python version to the image you choose. Your function will be rejected or might not run successfully if there is a mismatch between your local Python version and the Python version in the image. A differing bug release version (the “x” in Python version “2.7.x”) is fine.

If you need GPU support, choose one of the GPU-enabled images. All GPU-supporting images end with -gpu.

Current Images

Python 2.7.12, Ubuntu 16.04
Image: us.gcr.io/dl-ci-cd/images/tasks/public/py2/default:v2018.11.27
Date: 11/27/2018
Python highlights: GDAL, numpy, pandas, scikit-image, scikit-learn, scipy, Tensorflow, PyTorch
Other libraries and tools: GEOS 3.5.1, proj 4.9.2, FFTW 3.3.4
absl-py==0.6.1
affine==2.2.1
astor==0.7.1
astropy==2.0.5
atomicwrites==1.2.1
attrs==18.2.0
backports.functools-lru-cache==1.5
backports.weakref==1.0.post1
bleach==1.5.0
blosc==1.5.1
cachetools==2.0.1
certifi==2018.10.15
chardet==3.0.4
Click==7.0
click-plugins==1.0.4
cligj==0.5.0
cloudpickle==0.4.0
cryptography==1.2.3
cycler==0.10.0
decorator==4.3.0
descartes==1.1.0
descarteslabs==0.14.1
enum34==1.1.2
Fiona==1.7.11.post1
funcsigs==1.0.2
futures==3.2.0
gast==0.2.0
GDAL==2.2.2
geojson==2.4.1
geopandas==0.3.0
grpcio==1.16.1
h5py==2.7.1
html5lib==0.9999999
idna==2.7
ipaddress==1.0.16
Keras==2.1.5
kiwisolver==1.0.1
Markdown==3.0.1
matplotlib==2.2.3
mock==2.0.0
more-itertools==4.3.0
munch==2.3.2
networkx==2.1
numpy==1.11.0
pandas==0.22.0
pathlib2==2.3.2
pbr==5.1.1
Pillow==5.1.0
pluggy==0.8.0
protobuf==3.6.1
psutil==5.4.5
py==1.7.0
pyasn1==0.1.9
pyOpenSSL==0.15.1
pyparsing==2.3.0
pyproj==1.9.5.1
pytest==4.0.1
python-dateutil==2.7.5
pytz==2018.7
PyWavelets==1.0.1
PyYAML==3.13
rasterio==0.36.0
requests==2.20.1
scandir==1.9.0
scikit-image==0.13.1
scikit-learn==0.19.1
scipy==1.0.1
Shapely==1.6.4.post1
six==1.10.0
snuggs==1.4.2
subprocess32==3.5.3
tensorboard==1.7.0
tensorflow==1.7.0
termcolor==1.1.0
torch==0.4.0
torchvision==0.2.1
urllib3==1.24.1
Werkzeug==0.14.1
xarray==0.10.3
Python 2.7.12, Ubuntu 16.04, NVIDIA/CUDA GPU-enabled
Image: us.gcr.io/dl-ci-cd/images/tasks/public/py2-gpu/default:v2018.11.27
Date: 11/27/2018
Python highlights: GDAL, numpy, pandas, scikit-image, scikit-learn, scipy, Tensorflow, PyTorch
Other libraries and tools: cuda 9.0, GEOS 3.5.1, proj 4.9.2, FFTW 3.3.4
absl-py==0.6.1
affine==2.2.1
astor==0.7.1
astropy==2.0.5
atomicwrites==1.2.1
attrs==18.2.0
backports.functools-lru-cache==1.5
backports.weakref==1.0.post1
bleach==1.5.0
blosc==1.5.1
cachetools==2.0.1
certifi==2018.10.15
chardet==3.0.4
Click==7.0
click-plugins==1.0.4
cligj==0.5.0
cloudpickle==0.4.0
cryptography==1.2.3
cycler==0.10.0
decorator==4.3.0
descartes==1.1.0
descarteslabs==0.14.1
enum34==1.1.2
Fiona==1.7.11.post1
funcsigs==1.0.2
futures==3.2.0
gast==0.2.0
GDAL==2.2.2
geojson==2.4.1
geopandas==0.3.0
grpcio==1.16.1
h5py==2.7.1
html5lib==0.9999999
idna==2.7
ipaddress==1.0.16
Keras==2.1.5
kiwisolver==1.0.1
Markdown==3.0.1
matplotlib==2.2.3
mock==2.0.0
more-itertools==4.3.0
munch==2.3.2
networkx==2.1
numpy==1.11.0
pandas==0.22.0
pathlib2==2.3.2
pbr==5.1.1
Pillow==5.1.0
pluggy==0.8.0
protobuf==3.6.1
psutil==5.4.5
py==1.7.0
pyasn1==0.1.9
pyOpenSSL==0.15.1
pyparsing==2.3.0
pyproj==1.9.5.1
pytest==4.0.1
python-dateutil==2.7.5
pytz==2018.7
PyWavelets==1.0.1
PyYAML==3.13
rasterio==0.36.0
requests==2.20.1
scandir==1.9.0
scikit-image==0.13.1
scikit-learn==0.19.1
scipy==1.0.1
Shapely==1.6.4.post1
six==1.10.0
snuggs==1.4.2
subprocess32==3.5.3
tensorboard==1.7.0
tensorflow-gpu==1.7.0
termcolor==1.1.0
torch==0.4.0
torchvision==0.2.1
urllib3==1.24.1
Werkzeug==0.14.1
xarray==0.10.3
Python 3.4.9, Ubuntu 16.04
Image: us.gcr.io/dl-ci-cd/images/tasks/public/py3.4/default:v2018.11.27
Date: 11/27/2018
Python highlights: GDAL, numpy, pandas, scikit-image, scikit-learn, scipy, Tensorflow
Other libraries and tools: GEOS 3.5.1, proj 4.9.2, FFTW 3.3.4
absl-py==0.6.1
affine==2.2.1
astor==0.7.1
astropy==2.0.5
atomicwrites==1.2.1
attrs==18.2.0
bleach==1.5.0
blosc==1.5.1
cachetools==2.0.1
certifi==2018.10.15
chardet==3.0.4
Click==7.0
click-plugins==1.0.4
cligj==0.5.0
cloudpickle==0.4.0
cycler==0.10.0
decorator==4.3.0
descartes==1.1.0
descarteslabs==0.14.1
Fiona==1.7.11.post1
gast==0.2.0
GDAL==2.2.2
geojson==2.4.1
geopandas==0.3.0
grpcio==1.16.1
h5py==2.7.1
html5lib==0.9999999
idna==2.7
Keras==2.1.5
kiwisolver==1.0.1
Markdown==3.0.1
matplotlib==2.2.3
more-itertools==4.3.0
munch==2.3.2
networkx==2.1
numpy==1.11.0
pandas==0.22.0
pathlib2==2.3.2
Pillow==5.1.0
pluggy==0.8.0
protobuf==3.6.1
psutil==5.4.5
py==1.7.0
pycurl==7.43.0
pygobject==3.20.0
pyparsing==2.3.0
pyproj==1.9.5.1
pytest==4.0.1
python-apt==1.1.0b1+ubuntu0.16.4.2
python-dateutil==2.7.5
pytz==2018.7
PyWavelets==1.0.1
PyYAML==3.13
rasterio==0.36.0
requests==2.20.1
scandir==1.9.0
scikit-image==0.13.1
scikit-learn==0.19.1
scipy==1.0.1
Shapely==1.6.4.post1
six==1.11.0
snuggs==1.4.2
tensorboard==1.7.0
tensorflow==1.7.0
termcolor==1.1.0
urllib3==1.24.1
Werkzeug==0.14.1
xarray==0.10.3
Python 3.5.2, Ubuntu 16.04
Image: us.gcr.io/dl-ci-cd/images/tasks/public/py3.5/default:v2018.11.27
Date: 11/27/2018
Python highlights: GDAL, numpy, pandas, scikit-image, scikit-learn, scipy, Tensorflow, PyTorch
Other libraries and tools: GEOS 3.5.1, proj 4.9.2, FFTW 3.3.4
absl-py==0.6.1
affine==2.2.1
astor==0.7.1
astropy==2.0.5
atomicwrites==1.2.1
attrs==18.2.0
bleach==1.5.0
blosc==1.5.1
cachetools==2.0.1
certifi==2018.10.15
chardet==3.0.4
Click==7.0
click-plugins==1.0.4
cligj==0.5.0
cloudpickle==0.4.0
cycler==0.10.0
decorator==4.3.0
descartes==1.1.0
descarteslabs==0.14.1
Fiona==1.7.11.post1
gast==0.2.0
GDAL==2.2.2
geojson==2.4.1
geopandas==0.3.0
grpcio==1.16.1
h5py==2.7.1
html5lib==0.9999999
idna==2.7
Keras==2.1.5
kiwisolver==1.0.1
Markdown==3.0.1
matplotlib==3.0.2
more-itertools==4.3.0
munch==2.3.2
networkx==2.1
numpy==1.11.0
pandas==0.22.0
pathlib2==2.3.2
Pillow==5.1.0
pluggy==0.8.0
protobuf==3.6.1
psutil==5.4.5
py==1.7.0
pycurl==7.43.0
pygobject==3.20.0
pyparsing==2.3.0
pyproj==1.9.5.1
pytest==4.0.1
python-apt==1.1.0b1+ubuntu0.16.4.2
python-dateutil==2.7.5
pytz==2018.7
PyWavelets==1.0.1
PyYAML==3.13
rasterio==0.36.0
requests==2.20.1
scikit-image==0.13.1
scikit-learn==0.19.1
scipy==1.0.1
Shapely==1.6.4.post1
six==1.11.0
snuggs==1.4.2
tensorboard==1.7.0
tensorflow==1.7.0
termcolor==1.1.0
torch==0.4.0
torchvision==0.2.1
urllib3==1.24.1
Werkzeug==0.14.1
xarray==0.10.3
Python 3.5.2, Ubuntu 16.04, NVIDIA/CUDA GPU-enabled
Image: us.gcr.io/dl-ci-cd/images/tasks/public/py3.5-gpu/default:v2018.11.27
Date: 11/27/2018
Python highlights: GDAL, numpy, pandas, scikit-image, scikit-learn, scipy, Tensorflow, PyTorch
Other libraries and tools: cuda 9.0, GEOS 3.5.1, proj 4.9.2, FFTW 3.3.4
absl-py==0.6.1
affine==2.2.1
astor==0.7.1
astropy==2.0.5
atomicwrites==1.2.1
attrs==18.2.0
blosc==1.5.1
cachetools==2.0.1
certifi==2018.10.15
chardet==3.0.4
Click==7.0
click-plugins==1.0.4
cligj==0.5.0
cloudpickle==0.4.0
cycler==0.10.0
decorator==4.3.0
descartes==1.1.0
descarteslabs==0.14.1
Fiona==1.7.11.post1
gast==0.2.0
GDAL==2.2.2
geojson==2.4.1
geopandas==0.3.0
grpcio==1.16.1
h5py==2.7.1
idna==2.7
Keras==2.1.5
Keras-Applications==1.0.6
Keras-Preprocessing==1.0.5
kiwisolver==1.0.1
Markdown==3.0.1
matplotlib==3.0.2
more-itertools==4.3.0
munch==2.3.2
networkx==2.1
numpy==1.11.0
pandas==0.22.0
pathlib2==2.3.2
Pillow==5.1.0
pluggy==0.8.0
protobuf==3.6.1
psutil==5.4.5
py==1.7.0
pycurl==7.43.0
pygobject==3.20.0
pyparsing==2.3.0
pyproj==1.9.5.1
pytest==4.0.1
python-apt==1.1.0b1+ubuntu0.16.4.2
python-dateutil==2.7.5
pytz==2018.7
PyWavelets==1.0.1
PyYAML==3.13
rasterio==0.36.0
requests==2.20.1
scikit-image==0.13.1
scikit-learn==0.19.1
scipy==1.0.1
Shapely==1.6.4.post1
six==1.11.0
snuggs==1.4.2
tensorboard==1.12.0
tensorflow-gpu==1.12.0
termcolor==1.1.0
torch==0.4.0
torchvision==0.2.1
urllib3==1.24.1
Werkzeug==0.14.1
xarray==0.10.3
Python 3.6.7, Ubuntu 16.04
Image: us.gcr.io/dl-ci-cd/images/tasks/public/py3.6/default:v2018.11.27
Date: 11/27/2018
Python highlights: GDAL, numpy, pandas, scikit-image, scikit-learn, scipy, Tensorflow, PyTorch
Other libraries and tools: GEOS 3.5.1, proj 4.9.2, FFTW 3.3.4
absl-py==0.6.1
affine==2.2.1
astor==0.7.1
astropy==2.0.5
atomicwrites==1.2.1
attrs==18.2.0
bleach==1.5.0
blosc==1.5.1
cachetools==2.0.1
certifi==2018.10.15
chardet==3.0.4
Click==7.0
click-plugins==1.0.4
cligj==0.5.0
cloudpickle==0.4.0
cycler==0.10.0
decorator==4.3.0
descartes==1.1.0
descarteslabs==0.14.1
Fiona==1.7.11.post1
gast==0.2.0
GDAL==2.2.2
geojson==2.4.1
geopandas==0.3.0
grpcio==1.16.1
h5py==2.7.1
html5lib==0.9999999
idna==2.7
Keras==2.1.5
kiwisolver==1.0.1
Markdown==3.0.1
matplotlib==3.0.2
more-itertools==4.3.0
munch==2.3.2
networkx==2.1
numpy==1.11.0
pandas==0.22.0
Pillow==5.1.0
pluggy==0.8.0
protobuf==3.6.1
psutil==5.4.5
py==1.7.0
pycurl==7.43.0
pygobject==3.20.0
pyparsing==2.3.0
pyproj==1.9.5.1
pytest==4.0.1
python-apt==1.1.0b1+ubuntu0.16.4.2
python-dateutil==2.7.5
pytz==2018.7
PyWavelets==1.0.1
PyYAML==3.13
rasterio==0.36.0
requests==2.20.1
scikit-image==0.13.1
scikit-learn==0.19.1
scipy==1.0.1
Shapely==1.6.4.post1
six==1.11.0
snuggs==1.4.2
tensorboard==1.7.0
tensorflow==1.7.0
termcolor==1.1.0
torch==0.4.0
torchvision==0.2.1
urllib3==1.24.1
Werkzeug==0.14.1
xarray==0.10.3
Python 3.6.7, Ubuntu 16.04, NVIDIA/CUDA GPU-enabled
Image: us.gcr.io/dl-ci-cd/images/tasks/public/py3.6-gpu/default:v2018.11.27
Date: 11/27/2018
Python highlights: GDAL, numpy, pandas, scikit-image, scikit-learn, scipy, Tensorflow, PyTorch
Other libraries and tools: cuda 9.0, GEOS 3.5.1, proj 4.9.2, FFTW 3.3.4
absl-py==0.6.1
affine==2.2.1
astor==0.7.1
astropy==2.0.5
atomicwrites==1.2.1
attrs==18.2.0
blosc==1.5.1
cachetools==2.0.1
certifi==2018.10.15
chardet==3.0.4
Click==7.0
click-plugins==1.0.4
cligj==0.5.0
cloudpickle==0.4.0
cycler==0.10.0
decorator==4.3.0
descartes==1.1.0
descarteslabs==0.14.1
Fiona==1.7.11.post1
gast==0.2.0
GDAL==2.2.2
geojson==2.4.1
geopandas==0.3.0
grpcio==1.16.1
h5py==2.7.1
idna==2.7
Keras==2.1.5
Keras-Applications==1.0.6
Keras-Preprocessing==1.0.5
kiwisolver==1.0.1
Markdown==3.0.1
matplotlib==3.0.2
more-itertools==4.3.0
munch==2.3.2
networkx==2.1
numpy==1.11.0
pandas==0.22.0
Pillow==5.1.0
pluggy==0.8.0
protobuf==3.6.1
psutil==5.4.5
py==1.7.0
pycurl==7.43.0
pygobject==3.20.0
pyparsing==2.3.0
pyproj==1.9.5.1
pytest==4.0.1
python-apt==1.1.0b1+ubuntu0.16.4.2
python-dateutil==2.7.5
pytz==2018.7
PyWavelets==1.0.1
PyYAML==3.13
rasterio==0.36.0
requests==2.20.1
scikit-image==0.13.1
scikit-learn==0.19.1
scipy==1.0.1
Shapely==1.6.4.post1
six==1.11.0
snuggs==1.4.2
tensorboard==1.12.0
tensorflow-gpu==1.12.0
termcolor==1.1.0
torch==0.4.0
torchvision==0.2.1
urllib3==1.24.1
Werkzeug==0.14.1
xarray==0.10.3
Python 3.7.0, Ubuntu 18.04
Image: us.gcr.io/dl-ci-cd/images/tasks/public/py3.7/default:v2018.11.27
Date: 11/27/2018
Python highlights: GDAL, numpy, pandas, scikit-image, scikit-learn, scipy
Other libraries and tools: GEOS 3.6.2, proj 4.9.3, FFTW 3.3.7
affine==2.2.1
astropy==3.0.5
attrs==18.2.0
blosc==1.5.1
cachetools==2.0.1
certifi==2018.10.15
chardet==3.0.4
Click==7.0
click-plugins==1.0.4
cligj==0.5.0
cloudpickle==0.4.0
cycler==0.10.0
Cython==0.29
dask==0.20.2
decorator==4.3.0
descartes==1.1.0
descarteslabs==0.14.1
Fiona==1.7.11.post1
GDAL==2.2.2
geojson==2.4.1
geopandas==0.3.0
h5py==2.7.1
idna==2.6
kiwisolver==1.0.1
matplotlib==3.0.2
munch==2.3.2
networkx==2.1
numpy==1.15.4
pandas==0.23.4
Pillow==5.1.0
psutil==5.4.5
pygobject==3.26.1
pyparsing==2.3.0
pyproj==1.9.5.1
python-apt==1.6.3
python-dateutil==2.7.5
pytz==2018.7
PyWavelets==1.0.1
rasterio==1.0.9
requests==2.18.4
scikit-image==0.14.1
scikit-learn==0.20.0
scipy==1.0.1
Shapely==1.6.4.post1
six==1.11.0
snuggs==1.4.2
toolz==0.9.0
torch==0.4.1.post2
torchvision==0.2.1
urllib3==1.22
xarray==0.10.3