├── .dockerignore ├── .gitattributes ├── .github └── workflows │ ├── docker.yml │ └── test_notebook.yaml ├── .gitignore ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── cube-in-a-box-cloudformation.yml ├── docker-compose-prod.yml ├── docker-compose.yml ├── docs └── Detailed_Install.md ├── notebooks ├── ESRI_Land_Cover.ipynb ├── Indexing_More_Data.ipynb ├── NASADEM.ipynb ├── Sentinel_2.ipynb └── utils │ ├── DE_Africa_Logo_Stacked_RGB_small.jpg │ ├── deafrica_datahandling.py │ └── deafrica_plotting.py ├── parameters.json ├── products.csv ├── requirements.txt ├── scripts ├── install-docker.sh └── install-system-requirements.sh ├── setup.sh └── tests └── test_notebooks.py /.dockerignore: -------------------------------------------------------------------------------- 1 | data 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.github/workflows/docker.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Docker 3 | 4 | on: 5 | push: 6 | branches: 7 | - main 8 | paths: 9 | - "**" 10 | 11 | release: 12 | types: 13 | - created 14 | 15 | env: 16 | IMAGE_NAME: opendatacube/cube-in-a-box 17 | 18 | jobs: 19 | docker: 20 | runs-on: ubuntu-latest 21 | if: github.ref == 'refs/heads/main' || github.event_name == 'release' 22 | 23 | steps: 24 | - name: Checkout git 25 | uses: actions/checkout@v2 26 | with: 27 | fetch-depth: 0 28 | 29 | - name: Build and Push latest Docker image tag 30 | uses: whoan/docker-build-with-cache-action@v4 31 | with: 32 | image_name: ${{ env.IMAGE_NAME }} 33 | image_tag: latest 34 | username: gadockersvc 35 | password: ${{ secrets.GADOCKERSVC_PASSWORD }} 36 | -------------------------------------------------------------------------------- /.github/workflows/test_notebook.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Test Notebook 3 | 4 | on: 5 | push: 6 | branches: 7 | - main 8 | 9 | pull_request: 10 | branches: 11 | - main 12 | 13 | env: 14 | IMAGE_NAME: opendatacube/cube-in-a-box 15 | 16 | jobs: 17 | test: 18 | runs-on: ubuntu-latest 19 | steps: 20 | - name: Checkout git 21 | uses: actions/checkout@v2 22 | with: 23 | fetch-depth: 0 24 | 25 | - name: Set up cube-in-a-box 26 | run: | 27 | make setup 28 | 29 | - name: Run test 30 | run: | 31 | docker-compose up -d 32 | docker-compose exec -T jupyter bash "-c" "cd /; pytest tests/test_notebooks.py" -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | data 2 | .ipynb_checkpoints/ 3 | notebooks/.datacube.conf 4 | notebooks/.config/ 5 | notebooks/.local/ 6 | notebooks/.cache/ 7 | notebooks/.ipython/ 8 | notebooks/.jupyter/ 9 | notebooks/.python_history 10 | *.pyc 11 | notebooks/.cache/ 12 | notebooks/.datacube.conf 13 | notebooks/.ipynb_checkpoints/ 14 | notebooks/.ipython/ 15 | notebooks/.jupyter/ 16 | notebooks/.local/ 17 | notebooks/.bash_history 18 | notebooks/.python_history 19 | .env 20 | data 21 | __pycache__ 22 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM osgeo/gdal:ubuntu-small-3.6.3 2 | 3 | ENV DEBIAN_FRONTEND=noninteractive \ 4 | LC_ALL=C.UTF-8 \ 5 | LANG=C.UTF-8 \ 6 | TINI_VERSION=v0.19.0 7 | 8 | ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini /tini 9 | RUN chmod +x /tini 10 | 11 | RUN apt-get update && \ 12 | apt-get install -y \ 13 | build-essential \ 14 | git \ 15 | # For Psycopg2 16 | libpq-dev python3-dev \ 17 | python3-pip \ 18 | python3-wheel \ 19 | wget \ 20 | && apt-get autoclean \ 21 | && apt-get autoremove \ 22 | && rm -rf /var/lib/{apt,dpkg,cache,log} 23 | 24 | COPY requirements.txt /conf/ 25 | COPY products.csv /conf/ 26 | RUN pip3 install --no-cache-dir --requirement /conf/requirements.txt 27 | 28 | WORKDIR /notebooks 29 | 30 | ENTRYPOINT ["/tini", "--"] 31 | 32 | CMD ["jupyter", "notebook", "--allow-root", "--ip='0.0.0.0'", "--NotebookApp.token='secretpassword'"] 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Alex Leith 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | ## You can follow the steps below in order to get yourself a local ODC. 2 | ## Start by running `setup` then you should have a system that is fully configured 3 | ## 4 | ## Once running, you can access a Jupyter environment 5 | ## at 'http://localhost' with password 'secretpassword' 6 | .PHONY: help setup up down clean 7 | 8 | BBOX := 25,20,35,30 9 | 10 | help: ## Print this help 11 | @grep -E '^##.*$$' $(MAKEFILE_LIST) | cut -c'4-' 12 | @echo 13 | @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-10s\033[0m %s\n", $$1, $$2}' 14 | 15 | setup: build up init product index ## Run a full local/development setup 16 | setup-prod: up-prod init product index ## Run a full production setup 17 | 18 | up: ## 1. Bring up your Docker environment 19 | docker-compose up -d postgres 20 | docker-compose run checkdb 21 | docker-compose up -d jupyter 22 | 23 | init: ## 2. Prepare the database 24 | docker-compose exec -T jupyter datacube -v system init 25 | 26 | product: ## 3. Add a product definition for Sentinel-2 27 | docker-compose exec -T jupyter dc-sync-products /conf/products.csv 28 | 29 | 30 | index: ## 4. Index some data (Change extents with BBOX=',,,') 31 | docker-compose exec -T jupyter bash -c \ 32 | "stac-to-dc \ 33 | --bbox='$(BBOX)' \ 34 | --catalog-href='https://earth-search.aws.element84.com/v0/' \ 35 | --collections='sentinel-s2-l2a-cogs' \ 36 | --datetime='2021-06-01/2021-07-01'" 37 | docker-compose exec -T jupyter bash -c \ 38 | "stac-to-dc \ 39 | --catalog-href=https://planetarycomputer.microsoft.com/api/stac/v1/ \ 40 | --collections='io-lulc'" || true 41 | # doesnt support multipoligon https://github.com/opendatacube/odc-tools/issues/538 42 | docker-compose exec -T jupyter bash -c \ 43 | "stac-to-dc \ 44 | --catalog-href='https://planetarycomputer.microsoft.com/api/stac/v1/' \ 45 | --collections='nasadem' \ 46 | --bbox='$(BBOX)'" 47 | 48 | down: ## Bring down the system 49 | docker-compose down 50 | 51 | build: ## Rebuild the base image 52 | docker-compose pull 53 | docker-compose build 54 | 55 | shell: ## Start an interactive shell 56 | docker-compose exec jupyter bash 57 | 58 | clean: ## Delete everything 59 | docker-compose down --rmi all -v 60 | 61 | logs: ## Show the logs from the stack 62 | docker-compose logs --follow 63 | 64 | upload-s3: # Update S3 template (this is owned by Digital Earth Australia) 65 | aws s3 cp cube-in-a-box-cloudformation.yml s3://opendatacube-cube-in-a-box/ --acl public-read 66 | 67 | build-image: 68 | docker build --tag opendatacube/cube-in-a-box . 69 | 70 | push-image: 71 | docker push opendatacube/cube-in-a-box 72 | 73 | up-prod: ## Bring up production version 74 | docker-compose -f docker-compose-prod.yml pull 75 | docker-compose -f docker-compose.yml -f docker-compose-prod.yml up --detach postgres 76 | docker-compose run checkdb 77 | docker-compose -f docker-compose.yml -f docker-compose-prod.yml up --detach --no-build 78 | 79 | # This section can be used to deploy onto CloudFormation instead of the 'magic link' 80 | create-infra: 81 | aws cloudformation create-stack \ 82 | --region eu-west-1 \ 83 | --stack-name odc-test \ 84 | --template-body file://cube-in-a-box-cloudformation.yml \ 85 | --parameter file://parameters.json \ 86 | --tags Key=Name,Value=OpenDataCube \ 87 | --capabilities CAPABILITY_NAMED_IAM 88 | 89 | update-infra: 90 | aws cloudformation update-stack \ 91 | --stack-name eu-west-1 \ 92 | --template-body file://cube-in-a-box-cloudformation.yml \ 93 | --parameter file://parameters.json \ 94 | --tags Key=Name,Value=OpenDataCube \ 95 | --capabilities CAPABILITY_NAMED_IAM 96 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Cube in a Box 2 | 3 | The Cube in a Box is a simple way to run the [Open Data Cube](https://www.opendatacube.org). 4 | 5 | ## How to use: 6 | 7 | ### 1. Setup: 8 | 9 | **Checkout the Repo:** 10 | > `git clone https://github.com/opendatacube/cube-in-a-box.git` or `git clone git@github.com:opendatacube/cube-in-a-box.git` 11 | 12 | **First time users of Docker should run:** 13 | * `bash setup.sh` - This will get your system running and install everything you need. 14 | * Note that after this step you will either need to logout/login, or run the next step with `sudo` 15 | 16 | **If you already have `make` , `docker` and `docker-compose` installed. For a custom bounding box append `BBOX=,,,` to the end of the command.** 17 | * `make setup` or `make setup-prod` (for speed) 18 | * Custom bounding box: `make setup BBOX=-2,37,15,47` or `make setup-prod BBOX=-2,37,15,47` 19 | 20 | **If you do not have `make` installed and would rather run the commands individually run the following:** 21 | 22 | * Build a local environment: `docker-compose build` 23 | * Start a local environment: `docker-compose up` 24 | * Set up your local postgres database (after the above has finished) using: 25 | * `docker-compose exec jupyter datacube -v system init` 26 | * `docker-compose exec jupyter datacube product add https://raw.githubusercontent.com/digitalearthafrica/config/master/products/esa_s2_l2a.odc-product.yaml` 27 | * Index a default region with: 28 | * `docker-compose exec jupyter bash -c "stac-to-dc --bbox='25,20,35,30' --collections='sentinel-s2-l2a-cogs' --datetime='2020-01-01/2020-03-31'"` 29 | * Shutdown your local environment: 30 | * `docker-compose down` 31 | 32 | ### 2. Usage: 33 | View the Jupyter notebook `Sentinel_2.ipynb` at [http://localhost](http://localhost) using the password `secretpassword`. Note that you can index additional areas using the `Indexing_More_Data.ipynb` notebook. 34 | 35 | ## Deploying to AWS 36 | 37 | To deploy to AWS, you can either do it on the command line, with the AWS command line installed or the magic URL below and the AWS console. Detailed instructions are [available](docs/Detailed_Install.md). 38 | 39 | Once deployed, if you navigate to the IP of the deployed instance, you can access Jupyter with the password you set in the parameters.json file or in the AWS UI if you used the magic URL. 40 | 41 | ### Magic link 42 | 43 | [Launch a Cube in a Box](https://console.aws.amazon.com/cloudformation/home?#/stacks/new?stackName=cube-in-a-box&templateURL=http://opendatacube-cube-in-a-box.s3.amazonaws.com/cube-in-a-box-cloudformation.yml) 44 | 45 | You need to be logged in to the AWS Console deploy using this URL. Once logged in, click the link, and follow the prompts including settings a bounding box region of interest, EC2 instance type and password for Jupyter. 46 | 47 | ### Command line 48 | 49 | * Alter the parameters in the [parameters.json](./parameters.json) file 50 | * Run `make create-infra` 51 | * If you want to change the stack, you can do `make update-infra` (although it may be cleaner to delete and re-create the stack) 52 | -------------------------------------------------------------------------------- /cube-in-a-box-cloudformation.yml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: "2010-09-09" 2 | Metadata: 3 | License: Apache-2.0 4 | Description: "Open Data Cube template with EC2 instance and RDS." 5 | Parameters: 6 | KeyName: 7 | Description: Name of an existing EC2 KeyPair to enable SSH access to the instance 8 | Type: AWS::EC2::KeyPair::KeyName 9 | ConstraintDescription: Must be the name of an existing EC2 KeyPair. 10 | InstanceType: 11 | Description: WebServer EC2 instance type 12 | Type: String 13 | Default: t2.small 14 | AllowedValues: 15 | [ 16 | t2.small, 17 | t2.medium, 18 | t2.large, 19 | m1.large, 20 | m1.xlarge, 21 | m2.xlarge, 22 | c4.large, 23 | c4.xlarge, 24 | c4.2xlarge, 25 | g2.8xlarge, 26 | r3.large, 27 | r3.xlarge, 28 | ] 29 | ConstraintDescription: Must be a valid EC2 instance type. 30 | ExtentToIndex: 31 | Description: An extent to index for use in the Cube in a Box 32 | Type: String 33 | Default: "25,20,35,30" 34 | SecretPassword: 35 | Description: Password to open up the Jupyter notebook 36 | Type: String 37 | Default: "secretpassword" 38 | EC2InstanceName: 39 | Description: The name of the Cube in a Box EC2 instance 40 | Type: String 41 | Default: "Cube-in-a-Box" 42 | SSHLocation: 43 | Description: The IP address range that can be used to access the Cube in a Box 44 | Type: String 45 | MinLength: "9" 46 | MaxLength: "18" 47 | Default: 0.0.0.0/0 48 | AllowedPattern: (\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/(\d{1,2}) 49 | ConstraintDescription: must be a valid IP CIDR range of the form x.x.x.x/x. 50 | Region: 51 | Description: The AWS region to deploy in 52 | Type: String 53 | Default: us-west-2 54 | AllowedValues: [eu-west-1, us-west-2, ap-southeast-2] 55 | Mappings: 56 | RegionMap: 57 | eu-west-1: 58 | "HVM64": "ami-0ae0cb89fc578cd9c" 59 | us-west-2: 60 | "HVM64": "ami-04ef7170e45541f07" 61 | ap-southeast-2: 62 | "HVM64": "ami-033c54f661460cfd2" 63 | Resources: 64 | EC2Instance: 65 | Type: AWS::EC2::Instance 66 | Properties: 67 | InstanceType: !Ref "InstanceType" 68 | SecurityGroups: [!Ref "InstanceSecurityGroup"] 69 | KeyName: !Ref "KeyName" 70 | ImageId: !FindInMap [RegionMap, !Ref "AWS::Region", HVM64] 71 | IamInstanceProfile: !Ref ODCInstanceProfile 72 | BlockDeviceMappings: 73 | - DeviceName: /dev/sda1 74 | Ebs: 75 | VolumeSize: 50 76 | UserData: 77 | Fn::Base64: !Sub | 78 | #!/bin/bash -ex 79 | export DEBIAN_FRONTEND=noninteractive 80 | apt-get update && apt-get install -y wget unzip 81 | wget https://github.com/opendatacube/cube-in-a-box/archive/refs/heads/main.zip -O /home/ubuntu/archive.zip 82 | unzip /home/ubuntu/archive.zip -d /home/ubuntu 83 | bash /home/ubuntu/cube-in-a-box-main/setup.sh 84 | echo 'CIABPASSWORD=${SecretPassword}' > /home/ubuntu/cube-in-a-box-main/.env 85 | make -C /home/ubuntu/cube-in-a-box-main setup-prod BBOX="${ExtentToIndex}" 86 | Tags: 87 | - Key: "Name" 88 | Value: !Ref "EC2InstanceName" 89 | 90 | InstanceSecurityGroup: 91 | Type: AWS::EC2::SecurityGroup 92 | Properties: 93 | GroupDescription: Enable access 94 | SecurityGroupIngress: 95 | - IpProtocol: tcp 96 | FromPort: "22" 97 | ToPort: "22" 98 | CidrIp: !Ref "SSHLocation" 99 | - IpProtocol: tcp 100 | FromPort: "80" 101 | ToPort: "80" 102 | CidrIp: !Ref "SSHLocation" 103 | - IpProtocol: tcp 104 | FromPort: "8888" 105 | ToPort: "8888" 106 | CidrIp: !Ref "SSHLocation" 107 | 108 | ODCRole: 109 | Type: AWS::IAM::Role 110 | Properties: 111 | AssumeRolePolicyDocument: 112 | Version: "2012-10-17" 113 | Statement: 114 | - Effect: Allow 115 | Principal: 116 | Service: 117 | - ec2.amazonaws.com 118 | Action: 119 | - sts:AssumeRole 120 | Path: "/" 121 | RolePolicies: 122 | Type: AWS::IAM::Policy 123 | Properties: 124 | PolicyName: odc-policy 125 | PolicyDocument: 126 | Version: "2012-10-17" 127 | Statement: 128 | - Effect: Allow 129 | Action: ["S3:ListBucket"] 130 | Resource: ["arn:aws:s3:::landsat-pds"] 131 | - Effect: Allow 132 | Action: ["S3:GetObject"] 133 | Resource: ["arn:aws:s3:::landsat-pds/*"] 134 | Roles: 135 | - !Ref ODCRole 136 | ODCInstanceProfile: 137 | Type: AWS::IAM::InstanceProfile 138 | Properties: 139 | Path: "/" 140 | Roles: 141 | - !Ref ODCRole 142 | 143 | Outputs: 144 | InstanceId: 145 | Description: InstanceId of the newly created EC2 instance 146 | Value: !Ref "EC2Instance" 147 | AZ: 148 | Description: Availability Zone of the newly created EC2 instance 149 | Value: !GetAtt [EC2Instance, AvailabilityZone] 150 | PublicDNS: 151 | Description: Public DNSName of the newly created EC2 instance 152 | Value: !GetAtt [EC2Instance, PublicDnsName] 153 | PublicIP: 154 | Description: Public IP address of the newly created EC2 instance 155 | Value: !GetAtt [EC2Instance, PublicIp] 156 | -------------------------------------------------------------------------------- /docker-compose-prod.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | jupyter: 5 | image: opendatacube/cube-in-a-box:latest 6 | command: "jupyter notebook --allow-root --ip='0.0.0.0' -NotebookApp.token='${CIABPASSWORD}'" 7 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | postgres: 5 | image: kartoza/postgis:13-3.1 6 | environment: 7 | - POSTGRES_DB=opendatacube 8 | - POSTGRES_PASSWORD=opendatacubepassword 9 | - POSTGRES_USER=opendatacube 10 | volumes: 11 | - ./data/pg:/var/lib/postgresql/data 12 | ports: 13 | - 5432:5432 14 | restart: always 15 | 16 | jupyter: 17 | build: . 18 | environment: 19 | - DB_HOSTNAME=postgres 20 | - DB_USERNAME=opendatacube 21 | - DB_PASSWORD=opendatacubepassword 22 | - DB_DATABASE=opendatacube 23 | - AWS_NO_SIGN_REQUEST=true 24 | - STAC_API_URL=https://explorer.sandbox.dea.ga.gov.au/stac/ 25 | - CIABPASSWORD=${CIABPASSWORD:-secretpassword} 26 | links: 27 | - postgres:postgres 28 | ports: 29 | - "80:8888" 30 | volumes: 31 | - ./notebooks:/notebooks 32 | - ./tests:/tests 33 | - ./products.csv:/products.csv 34 | restart: always 35 | checkdb: 36 | image: schaffdev/port_check:latest 37 | environment: 38 | - CHECK_HOST=postgres 39 | - CHECK_PORT=5432 40 | links: 41 | - postgres:postgres 42 | -------------------------------------------------------------------------------- /docs/Detailed_Install.md: -------------------------------------------------------------------------------- 1 | # Detailed Install Instructions 2 | 3 | For most people running the `setup.sh` script will do all the below (except for Windows installs) 4 | 5 | ## Detailed Docker Install Instructions 6 | 7 | 1. Install Docker, and install Docker Compose. 8 | * Select your current OS from here [Install Docker](https://docs.docker.com/engine/install/) **Note that we currently do not support ARM machines** 9 | * For Windows and Mac OS X, docker compose comes with Docker Desktop. 10 | * For Windows make sure that you can run Linux containers. 11 | 2. Get the code for this repository here: [Git ZIP file](https://github.com/opendatacube/cube-in-a-box/archive/refs/heads/main.zip) 12 | 3. Extract the above ZIP file and go to that directory. 13 | * For Windows you will need to use PowerShell, by using the `cd` and `dir` commands, for example `cd C:/cubeinabox`. 14 | * For other OS's you can use your current shell. 15 | 4. Depnding on your system you can try one of the following commands: 16 | * First try running `make setup`, which will try setup everything on your machine. 17 | * On Mac the first time you try this it should request for you to install some tools. 18 | * On Windows this might not work at all, in which case you can continue to the next set of instructions. 19 | * On Linux this can be fixed by installing the make tools 20 | * If the above fails you can follow the following steps: 21 | 1. `docker-compose up` 22 | 2. When the a message like: `No web browser found: could not locate runnable browser` shows. Open a new shell and run the next commands 23 | 3. `docker-compose exec jupyter datacube -v system init` 24 | 4. `docker-compose exec jupyter datacube product add https://raw.githubusercontent.com/digitalearthafrica/config/master/products/esa_s2_l2a.odc-product.yaml` 25 | 4. `docker-compose exec jupyter bash -c "stac-to-dc --bbox='25,20,35,30' --collections='sentinel-s2-l2a-cogs' --datetime='2020-01-01/2020-03-31'"` 26 | 5. You should now be able to go to 27 | 6. Enter the password `secretpassword` 28 | 29 | 30 | ### Known Errors: 31 | 32 | * On Windows if you receive the error `Drive has not been shared` then you need to change this setting in Docker Settings > Shared Drives 33 | 34 | ## Detailed Amazon Web Services (AWS) Install Instructions 35 | If you are unfamiliar with AWS, this detailed guide can help you set up an AWS account, and create the necessary AWS components using the provided template from the [Magic Link](https://console.aws.amazon.com/cloudformation/home?#/stacks/new?stackName=cube-in-a-box&templateURL=https://s3-ap-southeast-2.amazonaws.com/cubeinabox/opendatacube-test.yml) as above. 36 | * First you will need an AWS account. While a 12 month free trail to several different AWS products is available, the computing power offered by the trial remote server is not enough to run demonstration ODC products. The cheapest option is currently a `t2.small` EC2 server, which will cost approximately $10 USD per month, if left running for the entire month. 37 | * Sign up for an [AWS account](https://portal.aws.amazon.com/billing/signup#/start). The account that is created is known as a root account. It has access to every capability of AWS, including your billing information. 38 | * To follow recommended security practice, [you should first create an Administrator IAM user](https://docs.aws.amazon.com/IAM/latest/UserGuide/getting-started_create-admin-group.html) for your main log-in account, which limits only access to billing and other large administrative policy changes. 39 | * The procedure listed in the above link will take you through creating the Administrator account, and when done, you should [log in as the Administrator](https://docs.aws.amazon.com/IAM/latest/UserGuide/getting-started_how-users-sign-in.html). 40 | * _It is now possible to begin testing CIAB immediately using the Administrator account. However, in a production ready environment, it would be best to create a second IAM account with access restricted to only the required functions of AWS. This part of the guide is a Work In Progress_. 41 | * To access your Cube in a Box, you will need a set of EC2 SSH keys. __Keys are region specific, so you must ensure your region when creating the CIAB is the same as the region used when creating the Keys.__ As the LS8 PDS is on US-West-2 Oregon, it is suggested you create your CIAB and keys on US-West-2 (Oregon) to facilitate fast loading of scenes. Change the region in the upper right dropdown. 42 | * Navigate to the [EC2 service management page, and create a new Key Pair](https://us-west-2.console.aws.amazon.com/ec2/v2/home?region=us-west-2#KeyPairs). The name of the key pair will be specified during the CIAB installation. Save the output .pem in a secure location. 43 | * You are now ready to launch the [CIAB Magic URL](https://console.aws.amazon.com/cloudformation/home?#/stacks/new?stackName=cube-in-a-box&templateURL=https://s3-ap-southeast-2.amazonaws.com/cubeinabox/opendatacube-test.yml), which navigates you to the CloudFormation Service, with a predefined template ready to launch. 44 | * The only detail you specifically need to select is the KeyName on page 2. Using the drop down, select the key name you specified earlier. 45 | * It is also recommended to change the SecretPassword, which will be used to log in to your Jupyter server. You may also choose a different EC2 Server type, with [costs listed here](https://aws.amazon.com/ec2/pricing/on-demand/). Please ensure US West (Oregon) is selected to get correct pricing. The R series of memory optimised instances [listed here](https://aws.amazon.com/ec2/instance-types/#Memory_Optimized) is a good starting point to look at for heavier workloads. Previous versions are [listed here](https://aws.amazon.com/ec2/previous-generation/) 46 | * Finally on page 2, change the ExtentToIndex to a small (1 degree x 1 degree or less) demonstration area. It is easy to add different or larger extents after familiarising yourself with the Open Data Cube. 47 | * No other settings are required to be changed, so you may click through, acknowledge the IAM resources notification, and Create your resource Stack. 48 | * If these are successful, you will be taken to the Stack manager. Note the Filter “Active” option can be changed to see In Progress or Failed stacks. 49 | * Click on the “Outputs” tab to see the publicDNS of your EC2 server, which can be navigated to in your browser. This contains your Jupyter server. It may take a minute or two for Jupyter to prepare itself (no more than 5 minutes). Your password will be the secretpassword you set earlier, or simply `secretpassword`. 50 | 51 | You should now see several notebook files, which can be run, edited and examined. 52 | -------------------------------------------------------------------------------- /notebooks/Indexing_More_Data.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Indexing More Data\n", 8 | "\n", 9 | "You can use this notebook to index an area of interest. Add a Lat and Lon center and a buffer (but note that you can only index 10,000 STAC items at a time) and run the steps below to add more data to your local ODC index." 10 | ] 11 | }, 12 | { 13 | "cell_type": "code", 14 | "execution_count": 4, 15 | "metadata": {}, 16 | "outputs": [], 17 | "source": [ 18 | "import os\n", 19 | "\n", 20 | "import datacube\n", 21 | "from satsearch import Search\n", 22 | "from odc.apps.dc_tools.stac_api_to_dc import stac_api_to_odc" 23 | ] 24 | }, 25 | { 26 | "cell_type": "code", 27 | "execution_count": 2, 28 | "metadata": {}, 29 | "outputs": [], 30 | "source": [ 31 | "lon_center = 15\n", 32 | "lat_center = -15\n", 33 | "buffer = 1\n", 34 | "\n", 35 | "bbox = [lon_center - buffer, lat_center - buffer, lon_center + buffer, lat_center + buffer]\n", 36 | "\n", 37 | "start_date = '2017-01-01'\n", 38 | "end_date = '2021-01-01'\n", 39 | "\n", 40 | "collections = ['sentinel-s2-l2a-cogs']\n", 41 | "\n", 42 | "config = {\n", 43 | " 'collections': collections,\n", 44 | " 'bbox': bbox,\n", 45 | " 'datetime': f\"{start_date}/{end_date}\"\n", 46 | "}\n", 47 | "\n", 48 | "STAC_API_URL = 'https://explorer.sandbox.dea.ga.gov.au/stac/'\n", 49 | "\n", 50 | "os.environ['STAC_API_URL'] = STAC_API_URL" 51 | ] 52 | }, 53 | { 54 | "cell_type": "code", 55 | "execution_count": 3, 56 | "metadata": {}, 57 | "outputs": [ 58 | { 59 | "name": "stdout", 60 | "output_type": "stream", 61 | "text": [ 62 | "Found 3936 items that can be indexed\n" 63 | ] 64 | } 65 | ], 66 | "source": [ 67 | "srch = Search().search(**config)\n", 68 | "found_items = srch.found()\n", 69 | "print(f\"Found {found_items} items that can be indexed\")" 70 | ] 71 | }, 72 | { 73 | "cell_type": "markdown", 74 | "metadata": {}, 75 | "source": [ 76 | "_Note that the following cell will potentially raise some errors or warnings, but should result in data being indexed still._" 77 | ] 78 | }, 79 | { 80 | "cell_type": "code", 81 | "execution_count": null, 82 | "metadata": {}, 83 | "outputs": [], 84 | "source": [ 85 | "dc = datacube.Datacube()\n", 86 | "\n", 87 | "indexed, failed, skipped = stac_api_to_odc(dc, None, False, False, config)\n", 88 | "print(f\"Indexed {indexed} out of {found_items} with {failed} failures and {skipped} skipped.\")" 89 | ] 90 | } 91 | ], 92 | "metadata": { 93 | "kernelspec": { 94 | "display_name": "Python 3.7.7 64-bit", 95 | "language": "python", 96 | "name": "python37764bit0fb2af63a77e49af99dd377937c85efa" 97 | }, 98 | "language_info": { 99 | "codemirror_mode": { 100 | "name": "ipython", 101 | "version": 3 102 | }, 103 | "file_extension": ".py", 104 | "mimetype": "text/x-python", 105 | "name": "python", 106 | "nbconvert_exporter": "python", 107 | "pygments_lexer": "ipython3", 108 | "version": "3.7.7-final" 109 | } 110 | }, 111 | "nbformat": 4, 112 | "nbformat_minor": 4 113 | } 114 | -------------------------------------------------------------------------------- /notebooks/Sentinel_2.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Sentinel-2 \n", 8 | "\n", 9 | "* **Products used:** \n", 10 | "[s2_l2a](https://explorer.digitalearth.africa/s2_l2a)\n", 11 | "\n", 12 | "Notebook copied from https://github.com/digitalearthafrica/deafrica-sandbox-notebooks\n" 13 | ] 14 | }, 15 | { 16 | "cell_type": "markdown", 17 | "metadata": {}, 18 | "source": [ 19 | "## Background\n", 20 | "\n", 21 | "Sentinel-2 is an Earth observation mission from the EU Copernicus Programme that systematically acquires optical imagery at high spatial resolution (up to 10 m for some bands).\n", 22 | "The mission is based on a constellation of two identical satellites in the same orbit, 180° apart for optimal coverage and data delivery.\n", 23 | "Together, they cover all Earth's land surfaces, large islands, inland and coastal waters every 3-5 days.\n", 24 | "\n", 25 | "Sentinel-2A was launched on 23 June 2015 and Sentinel-2B followed on 7 March 2017.\n", 26 | "Both of the Sentinel-2 satellites carry an innovative wide swath high-resolution multispectral imager with 13 spectral bands.\n", 27 | "For more information on the Sentinel-2 platforms and applications, check out the [European Space Agency website](http://www.esa.int/Applications/Observing_the_Earth/Copernicus/Overview4).\n", 28 | "\n", 29 | "Digital Earth Africa (DE Africa) provides [Sentinel-2, Level 2A](https://earth.esa.int/web/sentinel/technical-guides/sentinel-2-msi/level-2a-processing) surface reflectance data provided by ESA.\n", 30 | "Surface reflectance provides standardised optical datasets by using robust physical models to correct for variations in image radiance values due to atmospheric properties, as well as sun and sensor geometry.\n", 31 | "The resulting stack of surface reflectance grids are consistent over space and time, which is instrumental in identifying and quantifying environmental change.\n", 32 | "\n", 33 | "DE Africa provides one Sentinel-2 surface reflectance product:\n", 34 | "\n", 35 | "* **Sentinel-2** (i.e. `\"s2_l2a\"`): This product represents the 'definitive' source of high quality, analysis-ready Sentinel-2 surface reflectance data (Level 2A), and is available from 2017 to present. Sentinel-2A and Sentinel-2B satellite sensors are stored together under a single product name." 36 | ] 37 | }, 38 | { 39 | "cell_type": "markdown", 40 | "metadata": {}, 41 | "source": [ 42 | "Sentinel-2, Level 2A surface reflectance products have 13 spectral channels and one pixel quality band ([SCL](https://earth.esa.int/web/sentinel/technical-guides/sentinel-2-msi/level-2a/algorithm)):\n", 43 | "\n", 44 | "| Sentinel-2 bands | DE Africa band name | Band number | Central wavelength (nm) | Resolution (m) | Bandwidth (nm) |\n", 45 | "| -----------------|---------------|-------------|-------------------------|----------------|----------------|\n", 46 | "| Coastal aerosol | `coastal_aerosol` | 1 | 443 | 60 | 20 |\n", 47 | "| Blue | `blue` | 2 | 490 | 10 | 65 |\n", 48 | "| Green | `green` | 3 | 560 | 10 | 35 |\n", 49 | "| Red | `red` | 4 | 665 | 10 | 30 |\n", 50 | "| Vegetation red edge | `red_edge_1` | 5 | 705 | 20 | 15 |\n", 51 | "| Vegetation red edge | `red_edge_2` | 6 | 740 | 20 | 15 |\n", 52 | "| Vegetation red edge | `red_edge_3` | 7 | 783 | 20 | 20 |\n", 53 | "| NIR | `nir_1`, `nir` | 8 | 842 | 10 | 115 |\n", 54 | "| Narrow NIR | `nir_2`,`nir_narrow` | 8A | 865 | 20 | 20 |\n", 55 | "| Water vapour | `water_vapour` | 9 | 945 | 60 | 20 |\n", 56 | "| SWIR | `swir_1`, `swir_16` | 11 | 1610 | 20 | 90 |\n", 57 | "| SWIR | `swir_2`, `swir_22` | 12 | 2190 | 20 | 180 |\n", 58 | "| SCL | `SCL`, `qa`, `mask` | 13 | N/A | 20\n", 59 | "| AOT | `aerosol_optical_thickness` | N/A | N/A | 60\n", 60 | "\n", 61 | "These bands cover the visible, near-infrared and short-wave infrared wavelengths.\n", 62 | "\n", 63 | "!['Sentinel-2 spectral bands'](http://www.geosage.com/highview/figures/Sentinel2_Spectral_Bands.jpg)\n", 64 | "\n", 65 | "***" 66 | ] 67 | }, 68 | { 69 | "cell_type": "markdown", 70 | "metadata": {}, 71 | "source": [ 72 | "## Important details\n", 73 | "\n", 74 | "**Product details:**\n", 75 | "* Product name in the datacube: `\"s2_l2a\"`\n", 76 | "* Surface reflectance product (Level 2A)\n", 77 | " * Valid SR scaling range: `1 – 10,000 (0 is no-data)`\n", 78 | "* Spatial resolution: varies, see above table\n", 79 | "* [SCL](https://earth.esa.int/web/sentinel/technical-guides/sentinel-2-msi/level-2a/algorithm) used as pixel quality band\n", 80 | "* Universal Transverse Mercator (UTM) map projection. World Geodetic System (WGS) 84 datum\n", 81 | "* Stored in Cloud-Optimised GeoTIFF Format (COG)\n", 82 | "\n", 83 | "**Status:** \n", 84 | "\n", 85 | "DE Africa's Sentinel-2 data is processed to Level 2A using the [Sen2Cor](https://step.esa.int/main/third-party-plugins-2/sen2cor/) algorithm, stored as Cloud-Optimised Geotiffs, and is available over the entire African Continent. Sentinel-2A and Sentinel-2B satellite sensors are stored together under a single product name (`\"s2_l2a\"`). \n", 86 | "\n", 87 | "The catalogue is regularly updated to keep pace with new satellite passes.\n", 88 | "\n", 89 | "**Date-range**: \n", 90 | "\n", 91 | "2017 to Present\n", 92 | "\n", 93 | "\n", 94 | "**Minimum loading requirements:**\n", 95 | "```\n", 96 | "data = dc.load(product='s2_l2a',\n", 97 | " output_crs='epsg:6933',\n", 98 | " resolution=(-20, 20),\n", 99 | " **query)\n", 100 | "``` \n", 101 | "**Bands:**\n", 102 | "```\n", 103 | "coastal_aerosol\n", 104 | "blue \n", 105 | "green \n", 106 | "red\n", 107 | "red_edge_1\n", 108 | "red_edge_2\n", 109 | "red_edge_3\n", 110 | "nir_1\n", 111 | "nir_2\n", 112 | "swir_1 \n", 113 | "swir_2\n", 114 | "water_vapour\n", 115 | "SCL \n", 116 | "AOT\n", 117 | "```\n", 118 | "\n", 119 | "***" 120 | ] 121 | }, 122 | { 123 | "cell_type": "markdown", 124 | "metadata": {}, 125 | "source": [ 126 | "## Description\n", 127 | "\n", 128 | "This notebook will run through loading in Sentinel-2 satellite images.\n", 129 | "Topics covered include:\n", 130 | "\n", 131 | "* Using the native `dc.load()` function to load in Sentinel-2 data\n", 132 | "* Using the `load_ard()` wrapper function to load in a cloud and pixel-quality masked time series\n", 133 | "\n", 134 | "***" 135 | ] 136 | }, 137 | { 138 | "cell_type": "markdown", 139 | "metadata": {}, 140 | "source": [ 141 | "## Getting started\n", 142 | "\n", 143 | "To run this analysis, run all the cells in the notebook, starting with the \"Load packages\" cell." 144 | ] 145 | }, 146 | { 147 | "cell_type": "markdown", 148 | "metadata": {}, 149 | "source": [ 150 | "### Load packages" 151 | ] 152 | }, 153 | { 154 | "cell_type": "code", 155 | "execution_count": 1, 156 | "metadata": {}, 157 | "outputs": [], 158 | "source": [ 159 | "import sys\n", 160 | "sys.path.insert(1, '../utils/')" 161 | ] 162 | }, 163 | { 164 | "cell_type": "code", 165 | "execution_count": 2, 166 | "metadata": {}, 167 | "outputs": [], 168 | "source": [ 169 | "import datacube\n", 170 | "from utils.deafrica_datahandling import load_ard\n", 171 | "from utils.deafrica_plotting import rgb" 172 | ] 173 | }, 174 | { 175 | "cell_type": "markdown", 176 | "metadata": {}, 177 | "source": [ 178 | "### Connect to the datacube" 179 | ] 180 | }, 181 | { 182 | "cell_type": "code", 183 | "execution_count": 3, 184 | "metadata": {}, 185 | "outputs": [], 186 | "source": [ 187 | "dc = datacube.Datacube(app='Sentinel_2')" 188 | ] 189 | }, 190 | { 191 | "cell_type": "markdown", 192 | "metadata": {}, 193 | "source": [ 194 | "## Load Sentinel-2 data from the datacube\n", 195 | "\n", 196 | "We will load **Sentinel-2** data using two methods.\n", 197 | "Firstly, we will use [dc.load()](../Beginners_guide/03_Loading_data.ipynb) to return a time series of satellite images.\n", 198 | "Secondly, we will load a time series using the [load_ard()](../Frequently_used_code/Using_load_ard.ipynb) function, which is a wrapper function around the `dc.load` module.\n", 199 | "This function will load all the images from Sentinel-2 and then apply a cloud/pixel-quality mask.\n", 200 | "The returned `xarray.Dataset` will contain analysis ready images with the cloudy and invalid pixels masked out.\n", 201 | "\n", 202 | "You can change any of the parameters in the `query` object below to adjust the location, time, projection, or spatial resolution of the returned datasets.\n", 203 | "To learn more about querying, refer to the Beginner's guide notebook on [loading data](../Beginners_guide/03_Loading_data.ipynb).\n", 204 | "\n", 205 | "Sentinel-2 data is stored on file with a range of different coordinate reference systems or CRS (i.e. multiple UTM zones). \n", 206 | "The different satellite bands also have different resolutions (10 m, 20 m and 60 m). \n", 207 | "Because of this, all Sentinel-2 queries need to include the following two query parameters:\n", 208 | "\n", 209 | "* `output_crs`: This sets a consistent CRS that all Sentinel-2 data will be reprojected to, regardless of the UTM zone the individual image is stored in.\n", 210 | "* `resolution`: This sets the resolution that all Sentinel-2 images will be resampled to. \n", 211 | "\n", 212 | "> **Note:** Be aware that setting `resolution` to the highest available resolution (i.e. `(-10, 10)`) will downsample the coarser resolution 20 m and 60 m bands, which may introduce unintended artefacts into your analysis.\n", 213 | "It is typically best practice to set `resolution` to match the lowest resolution band being analysed. For example, if your analysis uses both 10 m and 20 m resolution bands, set `\"resolution\": (-20, 20)`." 214 | ] 215 | }, 216 | { 217 | "cell_type": "code", 218 | "execution_count": 4, 219 | "metadata": {}, 220 | "outputs": [], 221 | "source": [ 222 | "# Create a query object\n", 223 | "lat, lon = 22.821, 28.518\n", 224 | "buffer = 0.05\n", 225 | "\n", 226 | "query = {\n", 227 | " 'time': ('2021-06', '2021-10'),\n", 228 | " 'x': (lon - buffer, lon + buffer),\n", 229 | " 'y': (lat + buffer, lat - buffer),\n", 230 | " 'output_crs': 'epsg:6933',\n", 231 | " 'resolution':(-20,20),\n", 232 | "}" 233 | ] 234 | }, 235 | { 236 | "cell_type": "markdown", 237 | "metadata": {}, 238 | "source": [ 239 | "### Load Sentinel-2 using `dc.load()`\n", 240 | "\n", 241 | "The **Sentinel-2** product is called:\n", 242 | "\n", 243 | "* `s2_l2a`\n", 244 | "\n", 245 | "And contains images from both Sentinel-2 sensors, S2A and S2B\n", 246 | "\n", 247 | "We will now load in a time-series of satellite images from only Sentinel-2\n", 248 | "\n", 249 | "> **Note:** In this example, we include the `dask_chunks={}` parameter in order to \"lazy-load\" the data. The returned array will contain information about the data without directly loading it, which saves time in this instance. For more information on this, see the Beginner's guide notebook on [parallel processing with Dask](../Beginners_guide/06_Parallel_processing_with_dask.ipynb)." 250 | ] 251 | }, 252 | { 253 | "cell_type": "code", 254 | "execution_count": 5, 255 | "metadata": {}, 256 | "outputs": [ 257 | { 258 | "name": "stdout", 259 | "output_type": "stream", 260 | "text": [ 261 | "\n", 262 | "Dimensions: (time: 6, x: 483, y: 590)\n", 263 | "Coordinates:\n", 264 | " * time (time) datetime64[ns] 2021-06-05T08:53:54 ... 2021-06-30T08:...\n", 265 | " * y (y) float64 2.843e+06 2.843e+06 ... 2.831e+06 2.831e+06\n", 266 | " * x (x) float64 2.747e+06 2.747e+06 ... 2.756e+06 2.756e+06\n", 267 | " spatial_ref int32 6933\n", 268 | "Data variables:\n", 269 | " B01 (time, y, x) uint16 dask.array\n", 270 | " B02 (time, y, x) uint16 dask.array\n", 271 | " B03 (time, y, x) uint16 dask.array\n", 272 | " B04 (time, y, x) uint16 dask.array\n", 273 | " B05 (time, y, x) uint16 dask.array\n", 274 | " B06 (time, y, x) uint16 dask.array\n", 275 | " B07 (time, y, x) uint16 dask.array\n", 276 | " B08 (time, y, x) uint16 dask.array\n", 277 | " B8A (time, y, x) uint16 dask.array\n", 278 | " B09 (time, y, x) uint16 dask.array\n", 279 | " B11 (time, y, x) uint16 dask.array\n", 280 | " B12 (time, y, x) uint16 dask.array\n", 281 | " SCL (time, y, x) uint8 dask.array\n", 282 | " AOT (time, y, x) uint16 dask.array\n", 283 | " WVP (time, y, x) uint16 dask.array\n", 284 | "Attributes:\n", 285 | " crs: epsg:6933\n", 286 | " grid_mapping: spatial_ref\n" 287 | ] 288 | } 289 | ], 290 | "source": [ 291 | "#load data\n", 292 | "ds = dc.load(product='s2_l2a',\n", 293 | " dask_chunks={},\n", 294 | " **query)\n", 295 | "\n", 296 | "print(ds)" 297 | ] 298 | }, 299 | { 300 | "cell_type": "markdown", 301 | "metadata": {}, 302 | "source": [ 303 | "The returned dataset contains all of the bands available for Sentinel-2. These bands are named using the datasets native band-names. We can also specify which bands to return using band-name `aliases`, such as `red` or `nir`. We do this using the parameter `measurements`." 304 | ] 305 | }, 306 | { 307 | "cell_type": "code", 308 | "execution_count": 6, 309 | "metadata": { 310 | "scrolled": true 311 | }, 312 | "outputs": [ 313 | { 314 | "name": "stdout", 315 | "output_type": "stream", 316 | "text": [ 317 | "\n", 318 | "Dimensions: (time: 6, x: 483, y: 590)\n", 319 | "Coordinates:\n", 320 | " * time (time) datetime64[ns] 2021-06-05T08:53:54 ... 2021-06-30T08:...\n", 321 | " * y (y) float64 2.843e+06 2.843e+06 ... 2.831e+06 2.831e+06\n", 322 | " * x (x) float64 2.747e+06 2.747e+06 ... 2.756e+06 2.756e+06\n", 323 | " spatial_ref int32 6933\n", 324 | "Data variables:\n", 325 | " blue (time, y, x) uint16 dask.array\n", 326 | " green (time, y, x) uint16 dask.array\n", 327 | " red (time, y, x) uint16 dask.array\n", 328 | " nir (time, y, x) uint16 dask.array\n", 329 | " swir_1 (time, y, x) uint16 dask.array\n", 330 | "Attributes:\n", 331 | " crs: epsg:6933\n", 332 | " grid_mapping: spatial_ref\n" 333 | ] 334 | } 335 | ], 336 | "source": [ 337 | "bands = ['blue', 'green', 'red', 'nir', 'swir_1']\n", 338 | "\n", 339 | "ds = dc.load(product='s2_l2a',\n", 340 | " measurements=bands,\n", 341 | " dask_chunks={},\n", 342 | " **query)\n", 343 | "\n", 344 | "print(ds)" 345 | ] 346 | }, 347 | { 348 | "cell_type": "markdown", 349 | "metadata": {}, 350 | "source": [ 351 | "Once the load is complete, we can then analyse or plot the Sentinel-2 data. As you can see below, clouds and regions of no-data are visible." 352 | ] 353 | }, 354 | { 355 | "cell_type": "code", 356 | "execution_count": 7, 357 | "metadata": {}, 358 | "outputs": [ 359 | { 360 | "ename": "KeyError", 361 | "evalue": "1623747234000000000", 362 | "output_type": "error", 363 | "traceback": [ 364 | "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", 365 | "\u001b[0;31mKeyError\u001b[0m Traceback (most recent call last)", 366 | "Cell \u001b[0;32mIn[7], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[43mrgb\u001b[49m\u001b[43m(\u001b[49m\u001b[43mds\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mindex\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43m[\u001b[49m\u001b[38;5;241;43m-\u001b[39;49m\u001b[38;5;241;43m4\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m-\u001b[39;49m\u001b[38;5;241;43m3\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m-\u001b[39;49m\u001b[38;5;241;43m2\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m-\u001b[39;49m\u001b[38;5;241;43m1\u001b[39;49m\u001b[43m]\u001b[49m\u001b[43m)\u001b[49m\n", 367 | "File \u001b[0;32m/notebooks/utils/deafrica_plotting.py:237\u001b[0m, in \u001b[0;36mrgb\u001b[0;34m(ds, bands, index, index_dim, robust, percentile_stretch, col_wrap, size, aspect, savefig_path, savefig_kwargs, **kwargs)\u001b[0m\n\u001b[1;32m 234\u001b[0m \u001b[38;5;66;03m# If multiple index values are supplied, plot as a faceted plot\u001b[39;00m\n\u001b[1;32m 235\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mlen\u001b[39m(index) \u001b[38;5;241m>\u001b[39m \u001b[38;5;241m1\u001b[39m:\n\u001b[0;32m--> 237\u001b[0m img \u001b[38;5;241m=\u001b[39m \u001b[43mda\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mplot\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mimshow\u001b[49m\u001b[43m(\u001b[49m\u001b[43mrobust\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mrobust\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 238\u001b[0m \u001b[43m \u001b[49m\u001b[43mcol\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mindex_dim\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 239\u001b[0m \u001b[43m \u001b[49m\u001b[43mcol_wrap\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mcol_wrap\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 240\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43maspect_size_kwarg\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 241\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 243\u001b[0m \u001b[38;5;66;03m# If only one index is supplied, squeeze out index_dim and plot as a \u001b[39;00m\n\u001b[1;32m 244\u001b[0m \u001b[38;5;66;03m# single panel\u001b[39;00m\n\u001b[1;32m 245\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[1;32m 247\u001b[0m img \u001b[38;5;241m=\u001b[39m da\u001b[38;5;241m.\u001b[39msqueeze(dim\u001b[38;5;241m=\u001b[39mindex_dim)\u001b[38;5;241m.\u001b[39mplot\u001b[38;5;241m.\u001b[39mimshow(robust\u001b[38;5;241m=\u001b[39mrobust,\n\u001b[1;32m 248\u001b[0m \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39maspect_size_kwarg,\n\u001b[1;32m 249\u001b[0m \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs)\n", 368 | "File \u001b[0;32m/usr/local/lib/python3.10/dist-packages/xarray/plot/plot.py:827\u001b[0m, in \u001b[0;36m_plot2d..plotmethod\u001b[0;34m(_PlotMethods_obj, x, y, figsize, size, aspect, ax, row, col, col_wrap, xincrease, yincrease, add_colorbar, add_labels, vmin, vmax, cmap, colors, center, robust, extend, levels, infer_intervals, subplot_kws, cbar_ax, cbar_kwargs, xscale, yscale, xticks, yticks, xlim, ylim, norm, **kwargs)\u001b[0m\n\u001b[1;32m 825\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m arg \u001b[38;5;129;01min\u001b[39;00m [\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124m_PlotMethods_obj\u001b[39m\u001b[38;5;124m\"\u001b[39m, \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mnewplotfunc\u001b[39m\u001b[38;5;124m\"\u001b[39m, \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mkwargs\u001b[39m\u001b[38;5;124m\"\u001b[39m]:\n\u001b[1;32m 826\u001b[0m \u001b[38;5;28;01mdel\u001b[39;00m allargs[arg]\n\u001b[0;32m--> 827\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mnewplotfunc\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mallargs\u001b[49m\u001b[43m)\u001b[49m\n", 369 | "File \u001b[0;32m/usr/local/lib/python3.10/dist-packages/xarray/plot/plot.py:638\u001b[0m, in \u001b[0;36m_plot2d..newplotfunc\u001b[0;34m(darray, x, y, figsize, size, aspect, ax, row, col, col_wrap, xincrease, yincrease, add_colorbar, add_labels, vmin, vmax, cmap, center, robust, extend, levels, infer_intervals, colors, subplot_kws, cbar_ax, cbar_kwargs, xscale, yscale, xticks, yticks, xlim, ylim, norm, **kwargs)\u001b[0m\n\u001b[1;32m 636\u001b[0m \u001b[38;5;66;03m# Need the decorated plotting function\u001b[39;00m\n\u001b[1;32m 637\u001b[0m allargs[\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mplotfunc\u001b[39m\u001b[38;5;124m\"\u001b[39m] \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mglobals\u001b[39m()[plotfunc\u001b[38;5;241m.\u001b[39m\u001b[38;5;18m__name__\u001b[39m]\n\u001b[0;32m--> 638\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43m_easy_facetgrid\u001b[49m\u001b[43m(\u001b[49m\u001b[43mdarray\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mkind\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mdataarray\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mallargs\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 640\u001b[0m plt \u001b[38;5;241m=\u001b[39m import_matplotlib_pyplot()\n\u001b[1;32m 642\u001b[0m rgb \u001b[38;5;241m=\u001b[39m kwargs\u001b[38;5;241m.\u001b[39mpop(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mrgb\u001b[39m\u001b[38;5;124m\"\u001b[39m, \u001b[38;5;28;01mNone\u001b[39;00m)\n", 370 | "File \u001b[0;32m/usr/local/lib/python3.10/dist-packages/xarray/plot/facetgrid.py:644\u001b[0m, in \u001b[0;36m_easy_facetgrid\u001b[0;34m(data, plotfunc, kind, x, y, row, col, col_wrap, sharex, sharey, aspect, size, subplot_kws, ax, figsize, **kwargs)\u001b[0m\n\u001b[1;32m 641\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m g\u001b[38;5;241m.\u001b[39mmap_dataarray_line(plotfunc, x, y, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs)\n\u001b[1;32m 643\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m kind \u001b[38;5;241m==\u001b[39m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mdataarray\u001b[39m\u001b[38;5;124m\"\u001b[39m:\n\u001b[0;32m--> 644\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mg\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mmap_dataarray\u001b[49m\u001b[43m(\u001b[49m\u001b[43mplotfunc\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mx\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43my\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 646\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m kind \u001b[38;5;241m==\u001b[39m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mdataset\u001b[39m\u001b[38;5;124m\"\u001b[39m:\n\u001b[1;32m 647\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m g\u001b[38;5;241m.\u001b[39mmap_dataset(plotfunc, x, y, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs)\n", 371 | "File \u001b[0;32m/usr/local/lib/python3.10/dist-packages/xarray/plot/facetgrid.py:265\u001b[0m, in \u001b[0;36mFacetGrid.map_dataarray\u001b[0;34m(self, func, x, y, **kwargs)\u001b[0m\n\u001b[1;32m 261\u001b[0m func_kwargs\u001b[38;5;241m.\u001b[39mupdate({\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124madd_colorbar\u001b[39m\u001b[38;5;124m\"\u001b[39m: \u001b[38;5;28;01mFalse\u001b[39;00m, \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124madd_labels\u001b[39m\u001b[38;5;124m\"\u001b[39m: \u001b[38;5;28;01mFalse\u001b[39;00m})\n\u001b[1;32m 263\u001b[0m \u001b[38;5;66;03m# Get x, y labels for the first subplot\u001b[39;00m\n\u001b[1;32m 264\u001b[0m x, y \u001b[38;5;241m=\u001b[39m _infer_xy_labels(\n\u001b[0;32m--> 265\u001b[0m darray\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mdata\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mloc\u001b[49m\u001b[43m[\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mname_dicts\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mflat\u001b[49m\u001b[43m[\u001b[49m\u001b[38;5;241;43m0\u001b[39;49m\u001b[43m]\u001b[49m\u001b[43m]\u001b[49m,\n\u001b[1;32m 266\u001b[0m x\u001b[38;5;241m=\u001b[39mx,\n\u001b[1;32m 267\u001b[0m y\u001b[38;5;241m=\u001b[39my,\n\u001b[1;32m 268\u001b[0m imshow\u001b[38;5;241m=\u001b[39mfunc\u001b[38;5;241m.\u001b[39m\u001b[38;5;18m__name__\u001b[39m \u001b[38;5;241m==\u001b[39m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mimshow\u001b[39m\u001b[38;5;124m\"\u001b[39m,\n\u001b[1;32m 269\u001b[0m rgb\u001b[38;5;241m=\u001b[39mkwargs\u001b[38;5;241m.\u001b[39mget(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mrgb\u001b[39m\u001b[38;5;124m\"\u001b[39m, \u001b[38;5;28;01mNone\u001b[39;00m),\n\u001b[1;32m 270\u001b[0m )\n\u001b[1;32m 272\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m d, ax \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28mzip\u001b[39m(\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mname_dicts\u001b[38;5;241m.\u001b[39mflat, \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39maxes\u001b[38;5;241m.\u001b[39mflat):\n\u001b[1;32m 273\u001b[0m \u001b[38;5;66;03m# None is the sentinel value\u001b[39;00m\n\u001b[1;32m 274\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m d \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m:\n", 372 | "File \u001b[0;32m/usr/local/lib/python3.10/dist-packages/xarray/core/dataarray.py:198\u001b[0m, in \u001b[0;36m_LocIndexer.__getitem__\u001b[0;34m(self, key)\u001b[0m\n\u001b[1;32m 196\u001b[0m labels \u001b[38;5;241m=\u001b[39m indexing\u001b[38;5;241m.\u001b[39mexpanded_indexer(key, \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mdata_array\u001b[38;5;241m.\u001b[39mndim)\n\u001b[1;32m 197\u001b[0m key \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mdict\u001b[39m(\u001b[38;5;28mzip\u001b[39m(\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mdata_array\u001b[38;5;241m.\u001b[39mdims, labels))\n\u001b[0;32m--> 198\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mdata_array\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43msel\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkey\u001b[49m\u001b[43m)\u001b[49m\n", 373 | "File \u001b[0;32m/usr/local/lib/python3.10/dist-packages/xarray/core/dataarray.py:1149\u001b[0m, in \u001b[0;36mDataArray.sel\u001b[0;34m(self, indexers, method, tolerance, drop, **indexers_kwargs)\u001b[0m\n\u001b[1;32m 1068\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21msel\u001b[39m(\n\u001b[1;32m 1069\u001b[0m \u001b[38;5;28mself\u001b[39m,\n\u001b[1;32m 1070\u001b[0m indexers: Mapping[Hashable, Any] \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mNone\u001b[39;00m,\n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 1074\u001b[0m \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mindexers_kwargs: Any,\n\u001b[1;32m 1075\u001b[0m ) \u001b[38;5;241m-\u001b[39m\u001b[38;5;241m>\u001b[39m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mDataArray\u001b[39m\u001b[38;5;124m\"\u001b[39m:\n\u001b[1;32m 1076\u001b[0m \u001b[38;5;250m \u001b[39m\u001b[38;5;124;03m\"\"\"Return a new DataArray whose data is given by selecting index\u001b[39;00m\n\u001b[1;32m 1077\u001b[0m \u001b[38;5;124;03m labels along the specified dimension(s).\u001b[39;00m\n\u001b[1;32m 1078\u001b[0m \n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 1147\u001b[0m \n\u001b[1;32m 1148\u001b[0m \u001b[38;5;124;03m \"\"\"\u001b[39;00m\n\u001b[0;32m-> 1149\u001b[0m ds \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_to_temp_dataset\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43msel\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 1150\u001b[0m \u001b[43m \u001b[49m\u001b[43mindexers\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mindexers\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 1151\u001b[0m \u001b[43m \u001b[49m\u001b[43mdrop\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mdrop\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 1152\u001b[0m \u001b[43m \u001b[49m\u001b[43mmethod\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mmethod\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 1153\u001b[0m \u001b[43m \u001b[49m\u001b[43mtolerance\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mtolerance\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 1154\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mindexers_kwargs\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 1155\u001b[0m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 1156\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_from_temp_dataset(ds)\n", 374 | "File \u001b[0;32m/usr/local/lib/python3.10/dist-packages/xarray/core/dataset.py:2101\u001b[0m, in \u001b[0;36mDataset.sel\u001b[0;34m(self, indexers, method, tolerance, drop, **indexers_kwargs)\u001b[0m\n\u001b[1;32m 2039\u001b[0m \u001b[38;5;250m\u001b[39m\u001b[38;5;124;03m\"\"\"Returns a new dataset with each array indexed by tick labels\u001b[39;00m\n\u001b[1;32m 2040\u001b[0m \u001b[38;5;124;03malong the specified dimension(s).\u001b[39;00m\n\u001b[1;32m 2041\u001b[0m \n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 2098\u001b[0m \u001b[38;5;124;03mDataArray.sel\u001b[39;00m\n\u001b[1;32m 2099\u001b[0m \u001b[38;5;124;03m\"\"\"\u001b[39;00m\n\u001b[1;32m 2100\u001b[0m indexers \u001b[38;5;241m=\u001b[39m either_dict_or_kwargs(indexers, indexers_kwargs, \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124msel\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n\u001b[0;32m-> 2101\u001b[0m pos_indexers, new_indexes \u001b[38;5;241m=\u001b[39m \u001b[43mremap_label_indexers\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 2102\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mindexers\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mindexers\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mmethod\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mmethod\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mtolerance\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mtolerance\u001b[49m\n\u001b[1;32m 2103\u001b[0m \u001b[43m\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 2104\u001b[0m result \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39misel(indexers\u001b[38;5;241m=\u001b[39mpos_indexers, drop\u001b[38;5;241m=\u001b[39mdrop)\n\u001b[1;32m 2105\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m result\u001b[38;5;241m.\u001b[39m_overwrite_indexes(new_indexes)\n", 375 | "File \u001b[0;32m/usr/local/lib/python3.10/dist-packages/xarray/core/coordinates.py:396\u001b[0m, in \u001b[0;36mremap_label_indexers\u001b[0;34m(obj, indexers, method, tolerance, **indexers_kwargs)\u001b[0m\n\u001b[1;32m 389\u001b[0m indexers \u001b[38;5;241m=\u001b[39m either_dict_or_kwargs(indexers, indexers_kwargs, \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mremap_label_indexers\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n\u001b[1;32m 391\u001b[0m v_indexers \u001b[38;5;241m=\u001b[39m {\n\u001b[1;32m 392\u001b[0m k: v\u001b[38;5;241m.\u001b[39mvariable\u001b[38;5;241m.\u001b[39mdata \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28misinstance\u001b[39m(v, DataArray) \u001b[38;5;28;01melse\u001b[39;00m v\n\u001b[1;32m 393\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m k, v \u001b[38;5;129;01min\u001b[39;00m indexers\u001b[38;5;241m.\u001b[39mitems()\n\u001b[1;32m 394\u001b[0m }\n\u001b[0;32m--> 396\u001b[0m pos_indexers, new_indexes \u001b[38;5;241m=\u001b[39m \u001b[43mindexing\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mremap_label_indexers\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 397\u001b[0m \u001b[43m \u001b[49m\u001b[43mobj\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mv_indexers\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mmethod\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mmethod\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mtolerance\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mtolerance\u001b[49m\n\u001b[1;32m 398\u001b[0m \u001b[43m\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 399\u001b[0m \u001b[38;5;66;03m# attach indexer's coordinate to pos_indexers\u001b[39;00m\n\u001b[1;32m 400\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m k, v \u001b[38;5;129;01min\u001b[39;00m indexers\u001b[38;5;241m.\u001b[39mitems():\n", 376 | "File \u001b[0;32m/usr/local/lib/python3.10/dist-packages/xarray/core/indexing.py:270\u001b[0m, in \u001b[0;36mremap_label_indexers\u001b[0;34m(data_obj, indexers, method, tolerance)\u001b[0m\n\u001b[1;32m 268\u001b[0m coords_dtype \u001b[38;5;241m=\u001b[39m data_obj\u001b[38;5;241m.\u001b[39mcoords[dim]\u001b[38;5;241m.\u001b[39mdtype\n\u001b[1;32m 269\u001b[0m label \u001b[38;5;241m=\u001b[39m maybe_cast_to_coords_dtype(label, coords_dtype)\n\u001b[0;32m--> 270\u001b[0m idxr, new_idx \u001b[38;5;241m=\u001b[39m \u001b[43mconvert_label_indexer\u001b[49m\u001b[43m(\u001b[49m\u001b[43mindex\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mlabel\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mdim\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mmethod\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mtolerance\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 271\u001b[0m pos_indexers[dim] \u001b[38;5;241m=\u001b[39m idxr\n\u001b[1;32m 272\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m new_idx \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m:\n", 377 | "File \u001b[0;32m/usr/local/lib/python3.10/dist-packages/xarray/core/indexing.py:189\u001b[0m, in \u001b[0;36mconvert_label_indexer\u001b[0;34m(index, label, index_name, method, tolerance)\u001b[0m\n\u001b[1;32m 187\u001b[0m indexer \u001b[38;5;241m=\u001b[39m index\u001b[38;5;241m.\u001b[39mget_loc(label\u001b[38;5;241m.\u001b[39mitem())\n\u001b[1;32m 188\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[0;32m--> 189\u001b[0m indexer \u001b[38;5;241m=\u001b[39m \u001b[43mindex\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mget_loc\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 190\u001b[0m \u001b[43m \u001b[49m\u001b[43mlabel\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mitem\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mmethod\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mmethod\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mtolerance\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mtolerance\u001b[49m\n\u001b[1;32m 191\u001b[0m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 192\u001b[0m \u001b[38;5;28;01melif\u001b[39;00m label\u001b[38;5;241m.\u001b[39mdtype\u001b[38;5;241m.\u001b[39mkind \u001b[38;5;241m==\u001b[39m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mb\u001b[39m\u001b[38;5;124m\"\u001b[39m:\n\u001b[1;32m 193\u001b[0m indexer \u001b[38;5;241m=\u001b[39m label\n", 378 | "File \u001b[0;32m/usr/local/lib/python3.10/dist-packages/pandas/core/indexes/datetimes.py:700\u001b[0m, in \u001b[0;36mDatetimeIndex.get_loc\u001b[0;34m(self, key, method, tolerance)\u001b[0m\n\u001b[1;32m 696\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mindexer_at_time(key)\n\u001b[1;32m 698\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[1;32m 699\u001b[0m \u001b[38;5;66;03m# unrecognized type\u001b[39;00m\n\u001b[0;32m--> 700\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mKeyError\u001b[39;00m(key)\n\u001b[1;32m 702\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[1;32m 703\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m Index\u001b[38;5;241m.\u001b[39mget_loc(\u001b[38;5;28mself\u001b[39m, key, method, tolerance)\n", 379 | "\u001b[0;31mKeyError\u001b[0m: 1623747234000000000" 380 | ] 381 | }, 382 | { 383 | "data": { 384 | "image/png": "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", 385 | "text/plain": [ 386 | "
" 387 | ] 388 | }, 389 | "metadata": {}, 390 | "output_type": "display_data" 391 | } 392 | ], 393 | "source": [ 394 | "rgb(ds, index=[-4, -3, -2, -1])" 395 | ] 396 | }, 397 | { 398 | "cell_type": "markdown", 399 | "metadata": {}, 400 | "source": [ 401 | "### Load Sentinel-2 using `load_ard`\n", 402 | "\n", 403 | "This function will load images from Sentinel-2 and apply a cloud/pixel-quality mask.\n", 404 | "The result is an analysis ready dataset free of cloud, cloud-shadow, and missing data.\n", 405 | "\n", 406 | "You can find more information on this function from the [Using load ard](../Frequently_used_code/Using_load_ard.ipynb) notebook." 407 | ] 408 | }, 409 | { 410 | "cell_type": "code", 411 | "execution_count": 9, 412 | "metadata": {}, 413 | "outputs": [ 414 | { 415 | "name": "stdout", 416 | "output_type": "stream", 417 | "text": [ 418 | "Using pixel quality parameters for Sentinel 2\n", 419 | "Finding datasets\n", 420 | " s2_l2a\n", 421 | "Applying pixel quality/cloud mask\n", 422 | "Returning 6 time steps as a dask array\n", 423 | "\n", 424 | "Dimensions: (time: 6, x: 483, y: 590)\n", 425 | "Coordinates:\n", 426 | " * y (y) float64 2.843e+06 2.843e+06 ... 2.831e+06 2.831e+06\n", 427 | " * time (time) datetime64[ns] 2021-06-05T08:53:54 ... 2021-06-30T08:...\n", 428 | " * x (x) float64 2.747e+06 2.747e+06 ... 2.756e+06 2.756e+06\n", 429 | " spatial_ref int32 6933\n", 430 | "Data variables:\n", 431 | " blue (time, y, x) float32 dask.array\n", 432 | " green (time, y, x) float32 dask.array\n", 433 | " red (time, y, x) float32 dask.array\n", 434 | " nir (time, y, x) float32 dask.array\n", 435 | " swir_1 (time, y, x) float32 dask.array\n", 436 | "Attributes:\n", 437 | " crs: epsg:6933\n", 438 | " grid_mapping: spatial_ref\n" 439 | ] 440 | } 441 | ], 442 | "source": [ 443 | "ds = load_ard(dc=dc,\n", 444 | " products=['s2_l2a'],\n", 445 | " measurements=bands,\n", 446 | " dask_chunks={},\n", 447 | " **query)\n", 448 | "\n", 449 | "print(ds)" 450 | ] 451 | }, 452 | { 453 | "cell_type": "code", 454 | "execution_count": 10, 455 | "metadata": {}, 456 | "outputs": [ 457 | { 458 | "ename": "KeyError", 459 | "evalue": "1622883234000000000", 460 | "output_type": "error", 461 | "traceback": [ 462 | "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", 463 | "\u001b[0;31mKeyError\u001b[0m Traceback (most recent call last)", 464 | "Cell \u001b[0;32mIn[10], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[43mrgb\u001b[49m\u001b[43m(\u001b[49m\u001b[43mds\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mindex\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43m[\u001b[49m\u001b[38;5;241;43m0\u001b[39;49m\u001b[43m,\u001b[49m\u001b[38;5;241;43m1\u001b[39;49m\u001b[43m,\u001b[49m\u001b[38;5;241;43m2\u001b[39;49m\u001b[43m,\u001b[49m\u001b[38;5;241;43m3\u001b[39;49m\u001b[43m]\u001b[49m\u001b[43m)\u001b[49m\n", 465 | "File \u001b[0;32m/notebooks/utils/deafrica_plotting.py:220\u001b[0m, in \u001b[0;36mrgb\u001b[0;34m(ds, bands, index, index_dim, robust, percentile_stretch, col_wrap, size, aspect, savefig_path, savefig_kwargs, **kwargs)\u001b[0m\n\u001b[1;32m 217\u001b[0m \u001b[38;5;66;03m# If multiple index values are supplied, plot as a faceted plot\u001b[39;00m\n\u001b[1;32m 218\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mlen\u001b[39m(index) \u001b[38;5;241m>\u001b[39m \u001b[38;5;241m1\u001b[39m:\n\u001b[0;32m--> 220\u001b[0m img \u001b[38;5;241m=\u001b[39m \u001b[43mda\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mplot\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mimshow\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 221\u001b[0m \u001b[43m \u001b[49m\u001b[43mrobust\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mrobust\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 222\u001b[0m \u001b[43m \u001b[49m\u001b[43mcol\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mindex_dim\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 223\u001b[0m \u001b[43m \u001b[49m\u001b[43mcol_wrap\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mcol_wrap\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 224\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43maspect_size_kwarg\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 225\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 226\u001b[0m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 228\u001b[0m \u001b[38;5;66;03m# If only one index is supplied, squeeze out index_dim and plot as a\u001b[39;00m\n\u001b[1;32m 229\u001b[0m \u001b[38;5;66;03m# single panel\u001b[39;00m\n\u001b[1;32m 230\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[1;32m 232\u001b[0m img \u001b[38;5;241m=\u001b[39m da\u001b[38;5;241m.\u001b[39msqueeze(dim\u001b[38;5;241m=\u001b[39mindex_dim)\u001b[38;5;241m.\u001b[39mplot\u001b[38;5;241m.\u001b[39mimshow(\n\u001b[1;32m 233\u001b[0m robust\u001b[38;5;241m=\u001b[39mrobust, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39maspect_size_kwarg, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs\n\u001b[1;32m 234\u001b[0m )\n", 466 | "File \u001b[0;32m/usr/local/lib/python3.10/dist-packages/xarray/plot/plot.py:827\u001b[0m, in \u001b[0;36m_plot2d..plotmethod\u001b[0;34m(_PlotMethods_obj, x, y, figsize, size, aspect, ax, row, col, col_wrap, xincrease, yincrease, add_colorbar, add_labels, vmin, vmax, cmap, colors, center, robust, extend, levels, infer_intervals, subplot_kws, cbar_ax, cbar_kwargs, xscale, yscale, xticks, yticks, xlim, ylim, norm, **kwargs)\u001b[0m\n\u001b[1;32m 825\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m arg \u001b[38;5;129;01min\u001b[39;00m [\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124m_PlotMethods_obj\u001b[39m\u001b[38;5;124m\"\u001b[39m, \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mnewplotfunc\u001b[39m\u001b[38;5;124m\"\u001b[39m, \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mkwargs\u001b[39m\u001b[38;5;124m\"\u001b[39m]:\n\u001b[1;32m 826\u001b[0m \u001b[38;5;28;01mdel\u001b[39;00m allargs[arg]\n\u001b[0;32m--> 827\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mnewplotfunc\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mallargs\u001b[49m\u001b[43m)\u001b[49m\n", 467 | "File \u001b[0;32m/usr/local/lib/python3.10/dist-packages/xarray/plot/plot.py:638\u001b[0m, in \u001b[0;36m_plot2d..newplotfunc\u001b[0;34m(darray, x, y, figsize, size, aspect, ax, row, col, col_wrap, xincrease, yincrease, add_colorbar, add_labels, vmin, vmax, cmap, center, robust, extend, levels, infer_intervals, colors, subplot_kws, cbar_ax, cbar_kwargs, xscale, yscale, xticks, yticks, xlim, ylim, norm, **kwargs)\u001b[0m\n\u001b[1;32m 636\u001b[0m \u001b[38;5;66;03m# Need the decorated plotting function\u001b[39;00m\n\u001b[1;32m 637\u001b[0m allargs[\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mplotfunc\u001b[39m\u001b[38;5;124m\"\u001b[39m] \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mglobals\u001b[39m()[plotfunc\u001b[38;5;241m.\u001b[39m\u001b[38;5;18m__name__\u001b[39m]\n\u001b[0;32m--> 638\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43m_easy_facetgrid\u001b[49m\u001b[43m(\u001b[49m\u001b[43mdarray\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mkind\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mdataarray\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mallargs\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 640\u001b[0m plt \u001b[38;5;241m=\u001b[39m import_matplotlib_pyplot()\n\u001b[1;32m 642\u001b[0m rgb \u001b[38;5;241m=\u001b[39m kwargs\u001b[38;5;241m.\u001b[39mpop(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mrgb\u001b[39m\u001b[38;5;124m\"\u001b[39m, \u001b[38;5;28;01mNone\u001b[39;00m)\n", 468 | "File \u001b[0;32m/usr/local/lib/python3.10/dist-packages/xarray/plot/facetgrid.py:644\u001b[0m, in \u001b[0;36m_easy_facetgrid\u001b[0;34m(data, plotfunc, kind, x, y, row, col, col_wrap, sharex, sharey, aspect, size, subplot_kws, ax, figsize, **kwargs)\u001b[0m\n\u001b[1;32m 641\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m g\u001b[38;5;241m.\u001b[39mmap_dataarray_line(plotfunc, x, y, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs)\n\u001b[1;32m 643\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m kind \u001b[38;5;241m==\u001b[39m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mdataarray\u001b[39m\u001b[38;5;124m\"\u001b[39m:\n\u001b[0;32m--> 644\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mg\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mmap_dataarray\u001b[49m\u001b[43m(\u001b[49m\u001b[43mplotfunc\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mx\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43my\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 646\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m kind \u001b[38;5;241m==\u001b[39m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mdataset\u001b[39m\u001b[38;5;124m\"\u001b[39m:\n\u001b[1;32m 647\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m g\u001b[38;5;241m.\u001b[39mmap_dataset(plotfunc, x, y, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs)\n", 469 | "File \u001b[0;32m/usr/local/lib/python3.10/dist-packages/xarray/plot/facetgrid.py:265\u001b[0m, in \u001b[0;36mFacetGrid.map_dataarray\u001b[0;34m(self, func, x, y, **kwargs)\u001b[0m\n\u001b[1;32m 261\u001b[0m func_kwargs\u001b[38;5;241m.\u001b[39mupdate({\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124madd_colorbar\u001b[39m\u001b[38;5;124m\"\u001b[39m: \u001b[38;5;28;01mFalse\u001b[39;00m, \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124madd_labels\u001b[39m\u001b[38;5;124m\"\u001b[39m: \u001b[38;5;28;01mFalse\u001b[39;00m})\n\u001b[1;32m 263\u001b[0m \u001b[38;5;66;03m# Get x, y labels for the first subplot\u001b[39;00m\n\u001b[1;32m 264\u001b[0m x, y \u001b[38;5;241m=\u001b[39m _infer_xy_labels(\n\u001b[0;32m--> 265\u001b[0m darray\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mdata\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mloc\u001b[49m\u001b[43m[\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mname_dicts\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mflat\u001b[49m\u001b[43m[\u001b[49m\u001b[38;5;241;43m0\u001b[39;49m\u001b[43m]\u001b[49m\u001b[43m]\u001b[49m,\n\u001b[1;32m 266\u001b[0m x\u001b[38;5;241m=\u001b[39mx,\n\u001b[1;32m 267\u001b[0m y\u001b[38;5;241m=\u001b[39my,\n\u001b[1;32m 268\u001b[0m imshow\u001b[38;5;241m=\u001b[39mfunc\u001b[38;5;241m.\u001b[39m\u001b[38;5;18m__name__\u001b[39m \u001b[38;5;241m==\u001b[39m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mimshow\u001b[39m\u001b[38;5;124m\"\u001b[39m,\n\u001b[1;32m 269\u001b[0m rgb\u001b[38;5;241m=\u001b[39mkwargs\u001b[38;5;241m.\u001b[39mget(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mrgb\u001b[39m\u001b[38;5;124m\"\u001b[39m, \u001b[38;5;28;01mNone\u001b[39;00m),\n\u001b[1;32m 270\u001b[0m )\n\u001b[1;32m 272\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m d, ax \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28mzip\u001b[39m(\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mname_dicts\u001b[38;5;241m.\u001b[39mflat, \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39maxes\u001b[38;5;241m.\u001b[39mflat):\n\u001b[1;32m 273\u001b[0m \u001b[38;5;66;03m# None is the sentinel value\u001b[39;00m\n\u001b[1;32m 274\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m d \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m:\n", 470 | "File \u001b[0;32m/usr/local/lib/python3.10/dist-packages/xarray/core/dataarray.py:198\u001b[0m, in \u001b[0;36m_LocIndexer.__getitem__\u001b[0;34m(self, key)\u001b[0m\n\u001b[1;32m 196\u001b[0m labels \u001b[38;5;241m=\u001b[39m indexing\u001b[38;5;241m.\u001b[39mexpanded_indexer(key, \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mdata_array\u001b[38;5;241m.\u001b[39mndim)\n\u001b[1;32m 197\u001b[0m key \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mdict\u001b[39m(\u001b[38;5;28mzip\u001b[39m(\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mdata_array\u001b[38;5;241m.\u001b[39mdims, labels))\n\u001b[0;32m--> 198\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mdata_array\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43msel\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkey\u001b[49m\u001b[43m)\u001b[49m\n", 471 | "File \u001b[0;32m/usr/local/lib/python3.10/dist-packages/xarray/core/dataarray.py:1149\u001b[0m, in \u001b[0;36mDataArray.sel\u001b[0;34m(self, indexers, method, tolerance, drop, **indexers_kwargs)\u001b[0m\n\u001b[1;32m 1068\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21msel\u001b[39m(\n\u001b[1;32m 1069\u001b[0m \u001b[38;5;28mself\u001b[39m,\n\u001b[1;32m 1070\u001b[0m indexers: Mapping[Hashable, Any] \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mNone\u001b[39;00m,\n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 1074\u001b[0m \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mindexers_kwargs: Any,\n\u001b[1;32m 1075\u001b[0m ) \u001b[38;5;241m-\u001b[39m\u001b[38;5;241m>\u001b[39m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mDataArray\u001b[39m\u001b[38;5;124m\"\u001b[39m:\n\u001b[1;32m 1076\u001b[0m \u001b[38;5;250m \u001b[39m\u001b[38;5;124;03m\"\"\"Return a new DataArray whose data is given by selecting index\u001b[39;00m\n\u001b[1;32m 1077\u001b[0m \u001b[38;5;124;03m labels along the specified dimension(s).\u001b[39;00m\n\u001b[1;32m 1078\u001b[0m \n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 1147\u001b[0m \n\u001b[1;32m 1148\u001b[0m \u001b[38;5;124;03m \"\"\"\u001b[39;00m\n\u001b[0;32m-> 1149\u001b[0m ds \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_to_temp_dataset\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43msel\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 1150\u001b[0m \u001b[43m \u001b[49m\u001b[43mindexers\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mindexers\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 1151\u001b[0m \u001b[43m \u001b[49m\u001b[43mdrop\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mdrop\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 1152\u001b[0m \u001b[43m \u001b[49m\u001b[43mmethod\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mmethod\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 1153\u001b[0m \u001b[43m \u001b[49m\u001b[43mtolerance\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mtolerance\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 1154\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mindexers_kwargs\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 1155\u001b[0m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 1156\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_from_temp_dataset(ds)\n", 472 | "File \u001b[0;32m/usr/local/lib/python3.10/dist-packages/xarray/core/dataset.py:2101\u001b[0m, in \u001b[0;36mDataset.sel\u001b[0;34m(self, indexers, method, tolerance, drop, **indexers_kwargs)\u001b[0m\n\u001b[1;32m 2039\u001b[0m \u001b[38;5;250m\u001b[39m\u001b[38;5;124;03m\"\"\"Returns a new dataset with each array indexed by tick labels\u001b[39;00m\n\u001b[1;32m 2040\u001b[0m \u001b[38;5;124;03malong the specified dimension(s).\u001b[39;00m\n\u001b[1;32m 2041\u001b[0m \n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 2098\u001b[0m \u001b[38;5;124;03mDataArray.sel\u001b[39;00m\n\u001b[1;32m 2099\u001b[0m \u001b[38;5;124;03m\"\"\"\u001b[39;00m\n\u001b[1;32m 2100\u001b[0m indexers \u001b[38;5;241m=\u001b[39m either_dict_or_kwargs(indexers, indexers_kwargs, \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124msel\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n\u001b[0;32m-> 2101\u001b[0m pos_indexers, new_indexes \u001b[38;5;241m=\u001b[39m \u001b[43mremap_label_indexers\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 2102\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mindexers\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mindexers\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mmethod\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mmethod\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mtolerance\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mtolerance\u001b[49m\n\u001b[1;32m 2103\u001b[0m \u001b[43m\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 2104\u001b[0m result \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39misel(indexers\u001b[38;5;241m=\u001b[39mpos_indexers, drop\u001b[38;5;241m=\u001b[39mdrop)\n\u001b[1;32m 2105\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m result\u001b[38;5;241m.\u001b[39m_overwrite_indexes(new_indexes)\n", 473 | "File \u001b[0;32m/usr/local/lib/python3.10/dist-packages/xarray/core/coordinates.py:396\u001b[0m, in \u001b[0;36mremap_label_indexers\u001b[0;34m(obj, indexers, method, tolerance, **indexers_kwargs)\u001b[0m\n\u001b[1;32m 389\u001b[0m indexers \u001b[38;5;241m=\u001b[39m either_dict_or_kwargs(indexers, indexers_kwargs, \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mremap_label_indexers\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n\u001b[1;32m 391\u001b[0m v_indexers \u001b[38;5;241m=\u001b[39m {\n\u001b[1;32m 392\u001b[0m k: v\u001b[38;5;241m.\u001b[39mvariable\u001b[38;5;241m.\u001b[39mdata \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28misinstance\u001b[39m(v, DataArray) \u001b[38;5;28;01melse\u001b[39;00m v\n\u001b[1;32m 393\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m k, v \u001b[38;5;129;01min\u001b[39;00m indexers\u001b[38;5;241m.\u001b[39mitems()\n\u001b[1;32m 394\u001b[0m }\n\u001b[0;32m--> 396\u001b[0m pos_indexers, new_indexes \u001b[38;5;241m=\u001b[39m \u001b[43mindexing\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mremap_label_indexers\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 397\u001b[0m \u001b[43m \u001b[49m\u001b[43mobj\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mv_indexers\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mmethod\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mmethod\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mtolerance\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mtolerance\u001b[49m\n\u001b[1;32m 398\u001b[0m \u001b[43m\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 399\u001b[0m \u001b[38;5;66;03m# attach indexer's coordinate to pos_indexers\u001b[39;00m\n\u001b[1;32m 400\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m k, v \u001b[38;5;129;01min\u001b[39;00m indexers\u001b[38;5;241m.\u001b[39mitems():\n", 474 | "File \u001b[0;32m/usr/local/lib/python3.10/dist-packages/xarray/core/indexing.py:270\u001b[0m, in \u001b[0;36mremap_label_indexers\u001b[0;34m(data_obj, indexers, method, tolerance)\u001b[0m\n\u001b[1;32m 268\u001b[0m coords_dtype \u001b[38;5;241m=\u001b[39m data_obj\u001b[38;5;241m.\u001b[39mcoords[dim]\u001b[38;5;241m.\u001b[39mdtype\n\u001b[1;32m 269\u001b[0m label \u001b[38;5;241m=\u001b[39m maybe_cast_to_coords_dtype(label, coords_dtype)\n\u001b[0;32m--> 270\u001b[0m idxr, new_idx \u001b[38;5;241m=\u001b[39m \u001b[43mconvert_label_indexer\u001b[49m\u001b[43m(\u001b[49m\u001b[43mindex\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mlabel\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mdim\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mmethod\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mtolerance\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 271\u001b[0m pos_indexers[dim] \u001b[38;5;241m=\u001b[39m idxr\n\u001b[1;32m 272\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m new_idx \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m:\n", 475 | "File \u001b[0;32m/usr/local/lib/python3.10/dist-packages/xarray/core/indexing.py:189\u001b[0m, in \u001b[0;36mconvert_label_indexer\u001b[0;34m(index, label, index_name, method, tolerance)\u001b[0m\n\u001b[1;32m 187\u001b[0m indexer \u001b[38;5;241m=\u001b[39m index\u001b[38;5;241m.\u001b[39mget_loc(label\u001b[38;5;241m.\u001b[39mitem())\n\u001b[1;32m 188\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[0;32m--> 189\u001b[0m indexer \u001b[38;5;241m=\u001b[39m \u001b[43mindex\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mget_loc\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 190\u001b[0m \u001b[43m \u001b[49m\u001b[43mlabel\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mitem\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mmethod\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mmethod\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mtolerance\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mtolerance\u001b[49m\n\u001b[1;32m 191\u001b[0m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 192\u001b[0m \u001b[38;5;28;01melif\u001b[39;00m label\u001b[38;5;241m.\u001b[39mdtype\u001b[38;5;241m.\u001b[39mkind \u001b[38;5;241m==\u001b[39m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mb\u001b[39m\u001b[38;5;124m\"\u001b[39m:\n\u001b[1;32m 193\u001b[0m indexer \u001b[38;5;241m=\u001b[39m label\n", 476 | "File \u001b[0;32m/usr/local/lib/python3.10/dist-packages/pandas/core/indexes/datetimes.py:700\u001b[0m, in \u001b[0;36mDatetimeIndex.get_loc\u001b[0;34m(self, key, method, tolerance)\u001b[0m\n\u001b[1;32m 696\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mindexer_at_time(key)\n\u001b[1;32m 698\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[1;32m 699\u001b[0m \u001b[38;5;66;03m# unrecognized type\u001b[39;00m\n\u001b[0;32m--> 700\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mKeyError\u001b[39;00m(key)\n\u001b[1;32m 702\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[1;32m 703\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m Index\u001b[38;5;241m.\u001b[39mget_loc(\u001b[38;5;28mself\u001b[39m, key, method, tolerance)\n", 477 | "\u001b[0;31mKeyError\u001b[0m: 1622883234000000000" 478 | ] 479 | }, 480 | { 481 | "data": { 482 | "image/png": "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", 483 | "text/plain": [ 484 | "
" 485 | ] 486 | }, 487 | "metadata": {}, 488 | "output_type": "display_data" 489 | } 490 | ], 491 | "source": [ 492 | "rgb(ds, index=[0,1,2,3])" 493 | ] 494 | }, 495 | { 496 | "cell_type": "markdown", 497 | "metadata": {}, 498 | "source": [ 499 | "***\n", 500 | "\n", 501 | "## Additional information\n", 502 | "\n", 503 | "**License:** The code in this notebook is licensed under the [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0). \n", 504 | "Digital Earth Africa data is licensed under the [Creative Commons by Attribution 4.0](https://creativecommons.org/licenses/by/4.0/) license.\n", 505 | "\n", 506 | "**Contact:** If you need assistance, please post a question on the [Open Data Cube Slack channel](http://slack.opendatacube.org/) or on the [GIS Stack Exchange](https://gis.stackexchange.com/questions/ask?tags=open-data-cube) using the `open-data-cube` tag (you can view previously asked questions [here](https://gis.stackexchange.com/questions/tagged/open-data-cube)).\n", 507 | "If you would like to report an issue with this notebook, you can file one on [Github](https://github.com/digitalearthafrica/deafrica-sandbox-notebooks).\n", 508 | "\n", 509 | "**Last modified:** June 2020\n", 510 | "\n", 511 | "**Compatible datacube version:** " 512 | ] 513 | }, 514 | { 515 | "cell_type": "code", 516 | "execution_count": null, 517 | "metadata": {}, 518 | "outputs": [], 519 | "source": [ 520 | "print(datacube.__version__)" 521 | ] 522 | }, 523 | { 524 | "cell_type": "markdown", 525 | "metadata": {}, 526 | "source": [ 527 | "## Tags\n", 528 | "Browse all available tags on the DE Africa User Guide's [Tags Index]()" 529 | ] 530 | }, 531 | { 532 | "cell_type": "raw", 533 | "metadata": { 534 | "raw_mimetype": "text/restructuredtext" 535 | }, 536 | "source": [ 537 | "**Tags**: :index:`deafrica_datahandling`, :index:`deafrica_plotting`, :index:`load_ard`, :index:`rgb`, :Sentinel-2`" 538 | ] 539 | }, 540 | { 541 | "cell_type": "code", 542 | "execution_count": 6, 543 | "metadata": {}, 544 | "outputs": [ 545 | { 546 | "name": "stdout", 547 | "output_type": "stream", 548 | "text": [ 549 | "affine==2.4.0\r\n", 550 | "aiobotocore==1.0.7\r\n", 551 | "aiohttp==3.9.1\r\n", 552 | "aioitertools==0.11.0\r\n", 553 | "aiosignal==1.3.1\r\n", 554 | "anyio==3.7.1\r\n", 555 | "argon2-cffi==23.1.0\r\n", 556 | "argon2-cffi-bindings==21.2.0\r\n", 557 | "asttokens==2.4.1\r\n", 558 | "async-timeout==4.0.3\r\n", 559 | "attrs==23.1.0\r\n", 560 | "awscli==1.18.32\r\n", 561 | "Babel==2.13.1\r\n", 562 | "beautifulsoup4==4.12.2\r\n", 563 | "bleach==6.1.0\r\n", 564 | "boltons==23.1.1\r\n", 565 | "boto3==1.12.32\r\n", 566 | "botocore==1.15.32\r\n", 567 | "branca==0.7.0\r\n", 568 | "cachetools==5.3.2\r\n", 569 | "cattrs==23.2.3\r\n", 570 | "certifi==2023.11.17\r\n", 571 | "cffi==1.16.0\r\n", 572 | "cftime==1.6.3\r\n", 573 | "charset-normalizer==3.3.2\r\n", 574 | "ciso8601==2.3.1\r\n", 575 | "click==8.1.7\r\n", 576 | "click-plugins==1.1.1\r\n", 577 | "cligj==0.7.2\r\n", 578 | "cloudpickle==3.0.0\r\n", 579 | "colorama==0.4.3\r\n", 580 | "comm==0.2.0\r\n", 581 | "contourpy==1.2.0\r\n", 582 | "cycler==0.12.1\r\n", 583 | "dask==2023.12.0\r\n", 584 | "dask-image==2023.3.0\r\n", 585 | "datacube==1.8.17\r\n", 586 | "datadog==0.47.0\r\n", 587 | "debugpy==1.8.0\r\n", 588 | "decorator==5.1.1\r\n", 589 | "defusedxml==0.7.1\r\n", 590 | "deprecat==2.1.1\r\n", 591 | "distributed==2023.12.0\r\n", 592 | "docutils==0.15.2\r\n", 593 | "entrypoints==0.4\r\n", 594 | "eodatasets3==0.29.7\r\n", 595 | "exceptiongroup==1.2.0\r\n", 596 | "executing==2.0.1\r\n", 597 | "fastjsonschema==2.19.0\r\n", 598 | "Fiona==1.8.22\r\n", 599 | "folium==0.15.1\r\n", 600 | "fonttools==4.46.0\r\n", 601 | "frozenlist==1.4.0\r\n", 602 | "fsspec==2023.12.1\r\n", 603 | "GDAL==3.6.3\r\n", 604 | "GeoAlchemy2==0.14.2\r\n", 605 | "geopandas==0.13.2\r\n", 606 | "greenlet==3.0.1\r\n", 607 | "h5py==3.10.0\r\n", 608 | "idna==3.6\r\n", 609 | "imageio==2.33.0\r\n", 610 | "importlib-metadata==7.0.0\r\n", 611 | "importlib-resources==6.1.1\r\n", 612 | "iniconfig==2.0.0\r\n", 613 | "ipykernel==6.27.1\r\n", 614 | "ipyleaflet==0.18.0\r\n", 615 | "ipython==8.18.1\r\n", 616 | "ipython-genutils==0.2.0\r\n", 617 | "ipywidgets==8.1.1\r\n", 618 | "jedi==0.19.1\r\n", 619 | "Jinja2==3.1.2\r\n", 620 | "jmespath==0.10.0\r\n", 621 | "json5==0.9.14\r\n", 622 | "jsonschema==4.20.0\r\n", 623 | "jsonschema-specifications==2023.11.2\r\n", 624 | "jupyter==1.0.0\r\n", 625 | "jupyter-client==7.2.0\r\n", 626 | "jupyter-console==6.6.3\r\n", 627 | "jupyter-server==1.24.0\r\n", 628 | "jupyter-ui-poll==0.2.2\r\n", 629 | "jupyter_core==5.5.0\r\n", 630 | "jupyterlab==3.2.0\r\n", 631 | "jupyterlab-widgets==3.0.9\r\n", 632 | "jupyterlab_pygments==0.3.0\r\n", 633 | "jupyterlab_server==2.25.2\r\n", 634 | "kiwisolver==1.4.5\r\n", 635 | "lark==1.1.8\r\n", 636 | "lazy_loader==0.3\r\n", 637 | "lmdb==1.4.1\r\n", 638 | "locket==1.0.0\r\n", 639 | "MarkupSafe==2.1.3\r\n", 640 | "matplotlib==3.8.2\r\n", 641 | "matplotlib-inline==0.1.6\r\n", 642 | "mistune==3.0.2\r\n", 643 | "msgpack==1.0.7\r\n", 644 | "multidict==6.0.4\r\n", 645 | "munch==4.0.0\r\n", 646 | "nbclassic==0.5.6\r\n", 647 | "nbclient==0.9.0\r\n", 648 | "nbconvert==7.12.0\r\n", 649 | "nbformat==5.9.2\r\n", 650 | "nest-asyncio==1.5.8\r\n", 651 | "netCDF4==1.6.5\r\n", 652 | "networkx==3.2.1\r\n", 653 | "notebook==6.5.6\r\n", 654 | "notebook_shim==0.2.3\r\n", 655 | "numexpr==2.8.7\r\n", 656 | "numpy==1.26.2\r\n", 657 | "odc-algo==0.2.3\r\n", 658 | "odc-apps-dc-tools==0.2.15\r\n", 659 | "odc-cloud==0.2.4\r\n", 660 | "odc-dscache==0.2.2\r\n", 661 | "odc-io==0.2.2\r\n", 662 | "odc-stac==0.2.2\r\n", 663 | "odc-stats==1.0.11\r\n", 664 | "odc-ui==0.2.1\r\n", 665 | "packaging==23.2\r\n", 666 | "pandas==1.3.4\r\n", 667 | "pandocfilters==1.5.0\r\n", 668 | "parso==0.8.3\r\n", 669 | "partd==1.4.1\r\n", 670 | "pexpect==4.9.0\r\n", 671 | "Pillow==10.1.0\r\n", 672 | "PIMS==0.6.1\r\n", 673 | "platformdirs==4.1.0\r\n", 674 | "pluggy==1.3.0\r\n", 675 | "prometheus-client==0.19.0\r\n", 676 | "prompt-toolkit==3.0.41\r\n", 677 | "psutil==5.9.6\r\n", 678 | "psycopg2==2.9.9\r\n", 679 | "ptyprocess==0.7.0\r\n", 680 | "pure-eval==0.2.2\r\n", 681 | "pyasn1==0.5.1\r\n", 682 | "pycparser==2.21\r\n", 683 | "Pygments==2.17.2\r\n", 684 | "pyparsing==3.1.1\r\n", 685 | "pyproj==3.6.1\r\n", 686 | "pystac==1.9.0\r\n", 687 | "pystac-client==0.6.1\r\n", 688 | "pytest==7.4.3\r\n", 689 | "python-dateutil==2.7.5\r\n", 690 | "python-rapidjson==1.13\r\n", 691 | "pytz==2023.3.post1\r\n", 692 | "PyYAML==5.3.1\r\n", 693 | "pyzmq==24.0.1\r\n", 694 | "qtconsole==5.5.1\r\n", 695 | "QtPy==2.4.1\r\n", 696 | "rasterio==1.3.9\r\n", 697 | "referencing==0.31.1\r\n", 698 | "requests==2.31.0\r\n", 699 | "rio-stac==0.8.1\r\n", 700 | "rpds-py==0.13.2\r\n", 701 | "rsa==3.4.2\r\n", 702 | "ruamel.yaml==0.18.5\r\n", 703 | "ruamel.yaml.clib==0.2.8\r\n", 704 | "s3transfer==0.3.7\r\n", 705 | "sat-search==0.3.0\r\n", 706 | "sat-stac==0.4.1\r\n", 707 | "scikit-image==0.22.0\r\n", 708 | "scipy==1.11.4\r\n", 709 | "Send2Trash==1.8.2\r\n", 710 | "shapely==2.0.2\r\n", 711 | "six==1.16.0\r\n", 712 | "slicerator==1.1.0\r\n", 713 | "sniffio==1.3.0\r\n", 714 | "snuggs==1.4.7\r\n", 715 | "sortedcontainers==2.4.0\r\n", 716 | "soupsieve==2.5\r\n", 717 | "SQLAlchemy==1.4.50\r\n", 718 | "stack-data==0.6.3\r\n", 719 | "structlog==23.2.0\r\n", 720 | "tblib==3.0.0\r\n", 721 | "terminado==0.18.0\r\n", 722 | "testbook==0.4.2\r\n", 723 | "tifffile==2023.9.26\r\n", 724 | "tinycss2==1.2.1\r\n", 725 | "tomli==2.0.1\r\n", 726 | "toolz==0.12.0\r\n", 727 | "tornado==6.4\r\n", 728 | "tqdm==4.66.1\r\n", 729 | "traitlets==5.14.0\r\n", 730 | "traittypes==0.2.1\r\n", 731 | "typing_extensions==4.8.0\r\n", 732 | "urllib3==1.25.11\r\n", 733 | "urlpath==1.2.0\r\n", 734 | "wcwidth==0.2.12\r\n", 735 | "webencodings==0.5.1\r\n", 736 | "websocket-client==1.7.0\r\n", 737 | "wget==3.2\r\n", 738 | "widgetsnbextension==4.0.9\r\n", 739 | "wrapt==1.16.0\r\n", 740 | "xarray==0.16.0\r\n", 741 | "xyzservices==2023.10.1\r\n", 742 | "yarl==1.9.3\r\n", 743 | "zict==3.0.0\r\n", 744 | "zipp==3.17.0\r\n", 745 | "zstandard==0.22.0\r\n" 746 | ] 747 | } 748 | ], 749 | "source": [ 750 | "!pip freeze" 751 | ] 752 | }, 753 | { 754 | "cell_type": "code", 755 | "execution_count": 5, 756 | "metadata": {}, 757 | "outputs": [ 758 | { 759 | "name": "stdout", 760 | "output_type": "stream", 761 | "text": [ 762 | "Requirement already satisfied: dask==2023.12.0 in /usr/local/lib/python3.10/dist-packages (2023.12.0)\n", 763 | "Requirement already satisfied: importlib-metadata>=4.13.0 in /usr/local/lib/python3.10/dist-packages (from dask==2023.12.0) (7.0.0)\n", 764 | "Requirement already satisfied: packaging>=20.0 in /usr/local/lib/python3.10/dist-packages (from dask==2023.12.0) (23.2)\n", 765 | "Requirement already satisfied: toolz>=0.10.0 in /usr/local/lib/python3.10/dist-packages (from dask==2023.12.0) (0.12.0)\n", 766 | "Requirement already satisfied: fsspec>=2021.09.0 in /usr/local/lib/python3.10/dist-packages (from dask==2023.12.0) (2023.12.1)\n", 767 | "Requirement already satisfied: cloudpickle>=1.5.0 in /usr/local/lib/python3.10/dist-packages (from dask==2023.12.0) (3.0.0)\n", 768 | "Requirement already satisfied: partd>=1.2.0 in /usr/local/lib/python3.10/dist-packages (from dask==2023.12.0) (1.4.1)\n", 769 | "Requirement already satisfied: pyyaml>=5.3.1 in /usr/local/lib/python3.10/dist-packages (from dask==2023.12.0) (5.3.1)\n", 770 | "Requirement already satisfied: click>=8.1 in /usr/local/lib/python3.10/dist-packages (from dask==2023.12.0) (8.1.7)\n", 771 | "Requirement already satisfied: zipp>=0.5 in /usr/local/lib/python3.10/dist-packages (from importlib-metadata>=4.13.0->dask==2023.12.0) (3.17.0)\n", 772 | "Requirement already satisfied: locket in /usr/local/lib/python3.10/dist-packages (from partd>=1.2.0->dask==2023.12.0) (1.0.0)\n", 773 | "\u001b[33mWARNING: Running pip as the 'root' user can result in broken permissions and conflicting behaviour with the system package manager. It is recommended to use a virtual environment instead: https://pip.pypa.io/warnings/venv\u001b[0m\u001b[33m\n", 774 | "\u001b[0m" 775 | ] 776 | } 777 | ], 778 | "source": [ 779 | "!pip install dask==2023.12.0" 780 | ] 781 | }, 782 | { 783 | "cell_type": "code", 784 | "execution_count": null, 785 | "metadata": {}, 786 | "outputs": [], 787 | "source": [] 788 | } 789 | ], 790 | "metadata": { 791 | "kernelspec": { 792 | "display_name": "Python 3 (ipykernel)", 793 | "language": "python", 794 | "name": "python3" 795 | }, 796 | "language_info": { 797 | "codemirror_mode": { 798 | "name": "ipython", 799 | "version": 3 800 | }, 801 | "file_extension": ".py", 802 | "mimetype": "text/x-python", 803 | "name": "python", 804 | "nbconvert_exporter": "python", 805 | "pygments_lexer": "ipython3", 806 | "version": "3.10.12" 807 | }, 808 | "vscode": { 809 | "interpreter": { 810 | "hash": "36f1ec0750f296e3257d4c290a6c8ed00b14af3513565f9d7fc44fe655b3805b" 811 | } 812 | }, 813 | "widgets": { 814 | "application/vnd.jupyter.widget-state+json": { 815 | "state": { 816 | "1c7795a34baf4061a4170bcc11b19c01": { 817 | "model_module": "jupyter-leaflet", 818 | "model_module_version": "^0.11.1", 819 | "model_name": "LeafletZoomControlModel", 820 | "state": { 821 | "_model_module_version": "^0.11.1", 822 | "_view_count": null, 823 | "_view_module_version": "^0.11.1", 824 | "options": [ 825 | "position", 826 | "zoom_in_text", 827 | "zoom_in_title", 828 | "zoom_out_text", 829 | "zoom_out_title" 830 | ] 831 | } 832 | }, 833 | "d93582520e024946b4ef75064d893267": { 834 | "model_module": "jupyter-leaflet", 835 | "model_module_version": "^0.11.1", 836 | "model_name": "LeafletAttributionControlModel", 837 | "state": { 838 | "_model_module_version": "^0.11.1", 839 | "_view_count": null, 840 | "_view_module_version": "^0.11.1", 841 | "options": [ 842 | "position", 843 | "prefix" 844 | ], 845 | "position": "bottomright", 846 | "prefix": "Leaflet" 847 | } 848 | } 849 | }, 850 | "version_major": 2, 851 | "version_minor": 0 852 | } 853 | } 854 | }, 855 | "nbformat": 4, 856 | "nbformat_minor": 4 857 | } 858 | -------------------------------------------------------------------------------- /notebooks/utils/DE_Africa_Logo_Stacked_RGB_small.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opendatacube/cube-in-a-box/5091e6b8fd3b483056a93cc14c512838c25414dc/notebooks/utils/DE_Africa_Logo_Stacked_RGB_small.jpg -------------------------------------------------------------------------------- /notebooks/utils/deafrica_datahandling.py: -------------------------------------------------------------------------------- 1 | ## deafrica_datahandling.py 2 | ''' 3 | Description: This file contains a set of python functions for handling 4 | Digital Earth Africa data. 5 | 6 | License: The code in this notebook is licensed under the Apache License, 7 | Version 2.0 (https://www.apache.org/licenses/LICENSE-2.0). Digital Earth 8 | Africa data is licensed under the Creative Commons by Attribution 4.0 9 | license (https://creativecommons.org/licenses/by/4.0/). 10 | 11 | Contact: If you need assistance, please post a question on the Open Data 12 | Cube Slack channel (http://slack.opendatacube.org/) or on the GIS Stack 13 | Exchange (https://gis.stackexchange.com/questions/ask?tags=open-data-cube) 14 | using the `open-data-cube` tag (you can view previously asked questions 15 | here: https://gis.stackexchange.com/questions/tagged/open-data-cube). 16 | 17 | If you would like to report an issue with this script, you can file one on 18 | Github: https://github.com/digitalearthafrica/deafrica-sandbox-notebooks/issues/new 19 | 20 | Functions included: 21 | load_ard 22 | load_masked_FC 23 | array_to_geotiff 24 | mostcommon_utm 25 | download_unzip 26 | wofs_fuser 27 | dilate 28 | first 29 | last 30 | nearest 31 | 32 | Last modified: March 2020 33 | 34 | ''' 35 | 36 | # Import required packages 37 | import os 38 | from osgeo import gdal 39 | import requests 40 | import zipfile 41 | import warnings 42 | import numpy as np 43 | import xarray as xr 44 | import pandas as pd 45 | import datetime 46 | import pytz 47 | 48 | from collections import Counter 49 | from datacube.utils import masking 50 | from scipy.ndimage import binary_dilation 51 | from copy import deepcopy 52 | import odc.algo 53 | 54 | 55 | def _dc_query_only(**kw): 56 | """ Remove load-only parameters, the rest can be passed to Query 57 | 58 | Returns 59 | ======= 60 | 61 | dict of query parameters 62 | """ 63 | 64 | def _impl(measurements=None, 65 | output_crs=None, 66 | resolution=None, 67 | resampling=None, 68 | skip_broken_datasets=None, 69 | dask_chunks=None, 70 | fuse_func=None, 71 | align=None, 72 | datasets=None, 73 | progress_cbk=None, 74 | group_by=None, 75 | **query): 76 | return query 77 | 78 | return _impl(**kw) 79 | 80 | 81 | def _common_bands(dc, products): 82 | """ 83 | Takes a list of products and returns a list of measurements/bands 84 | that are present in all products 85 | Returns 86 | ------- 87 | List of band names 88 | """ 89 | common = None 90 | bands = None 91 | 92 | for p in products: 93 | p = dc.index.products.get_by_name(p) 94 | if common is None: 95 | common = set(p.measurements) 96 | bands = list(p.measurements) 97 | else: 98 | common = common.intersection(set(p.measurements)) 99 | return [band for band in bands if band in common] 100 | 101 | 102 | def load_ard(dc, 103 | products=None, 104 | min_gooddata=0.0, 105 | pq_categories_s2=['vegetation','snow or ice', 106 | 'water','bare soils', 107 | 'unclassified', 'dark area pixels'], 108 | pq_categories_ls=None, 109 | mask_pixel_quality=True, 110 | ls7_slc_off=True, 111 | predicate=None, 112 | dtype='auto', 113 | scaling='raw', 114 | **kwargs): 115 | 116 | ''' 117 | Loads and combines Landsat Collections 1 or 2, and Sentinel-2 for 118 | multiple sensors (i.e. ls5t, ls7e and ls8c for Landsat; s2a and s2b for Sentinel-2), 119 | optionally applies pixel quality masks, and drops time steps that 120 | contain greater than a minimum proportion of good quality (e.g. non- 121 | cloudy or shadowed) pixels. 122 | The function supports loading the following DE Africa products: 123 | 124 | ls5_usgs_sr_scene 125 | ls7_usgs_sr_scene 126 | ls8_usgs_sr_scene 127 | usgs_ls8c_level2_2 128 | ga_ls8c_fractional_cover_2 129 | s2_l2a 130 | 131 | Last modified: March 2020 132 | 133 | Parameters 134 | ---------- 135 | dc : datacube Datacube object 136 | The Datacube to connect to, i.e. `dc = datacube.Datacube()`. 137 | This allows you to also use development datacubes if required. 138 | products : list 139 | A list of product names to load data from. Valid options are 140 | Landsat C1: ['ls5_usgs_sr_scene', 'ls7_usgs_sr_scene', 'ls8_usgs_sr_scene'], 141 | Landsat C2: ['usgs_ls8c_level2_2'] 142 | Sentinel-2: ['s2_l2a'] 143 | min_gooddata : float, optional 144 | An optional float giving the minimum percentage of good quality 145 | pixels required for a satellite observation to be loaded. 146 | Defaults to 0.0 which will return all observations regardless of 147 | pixel quality (set to e.g. 0.99 to return only observations with 148 | more than 99% good quality pixels). 149 | pq_categories_s2 : list, optional 150 | An optional list of Sentinel-2 Scene Classification Layer (SCL) names 151 | to treat as good quality observations in the above `min_gooddata` 152 | calculation. The default is ['vegetation','snow or ice','water', 153 | 'bare soils','unclassified', 'dark area pixels'] which will return 154 | non-cloudy or non-shadowed land, snow, water, veg, and non-veg pixels. 155 | pq_categories_ls : dict, optional 156 | An optional dictionary that is used to generate a good quality 157 | pixel mask from the selected USGS product's pixel quality band (i.e. 158 | 'pixel_qa' for USGS Collection 1, and 'quality_l2_aerosol' for 159 | USGS Collection 2). This mask is used for both masking out low 160 | quality pixels (e.g. cloud or shadow), and for dropping 161 | observations entirely based on the above `min_gooddata` 162 | calculation. Default is None, which will apply the following mask 163 | for USGS Collection 1: `{'cloud': 'no_cloud', 'cloud_shadow': 164 | 'no_cloud_shadow', 'nodata': False}`, and for USGS Collection 2: 165 | `{'cloud_shadow': 'not_cloud_shadow', 'cloud_or_cirrus': 166 | 'not_cloud_or_cirrus', 'nodata': False}. 167 | mask_pixel_quality : bool, optional 168 | An optional boolean indicating whether to apply the good data 169 | mask to all observations that were not filtered out for having 170 | less good quality pixels than `min_gooddata`. E.g. if 171 | `min_gooddata=0.99`, the filtered observations may still contain 172 | up to 1% poor quality pixels. The default of False simply 173 | returns the resulting observations without masking out these 174 | pixels; True masks them and sets them to NaN using the good data 175 | mask. This will convert numeric values to floating point values 176 | which can cause memory issues, set to False to prevent this. 177 | ls7_slc_off : bool, optional 178 | An optional boolean indicating whether to include data from 179 | after the Landsat 7 SLC failure (i.e. SLC-off). Defaults to 180 | True, which keeps all Landsat 7 observations > May 31 2003. 181 | predicate : function, optional 182 | An optional function that can be passed in to restrict the 183 | datasets that are loaded by the function. A filter function 184 | should take a `datacube.model.Dataset` object as an input (i.e. 185 | as returned from `dc.find_datasets`), and return a boolean. 186 | For example, a filter function could be used to return True on 187 | only datasets acquired in January: 188 | `dataset.time.begin.month == 1` 189 | dtype : string, optional 190 | An optional parameter that controls the data type/dtype that 191 | layers are coerced to after loading. Valid values: 'native', 192 | 'auto', 'float{16|32|64}'. When 'auto' is used, the data will be 193 | converted to `float32` if masking is used, otherwise data will 194 | be returned in the native data type of the data. Be aware that 195 | if data is loaded in its native dtype, nodata and masked 196 | pixels will be returned with the data's native nodata value 197 | (typically -999), not NaN. 198 | scaling : str, optional 199 | If 'normalised', then surface reflectance values are scaled from 200 | their original values to 0-1. If 'raw' then dataset is returned 201 | in its native scaling. WARNING: USGS Landsat Collection 2 202 | surface reflectance values have an offset so normliaed band indices 203 | will return non-sensical results if setting scaling='raw'. 204 | **kwargs : 205 | A set of keyword arguments to `dc.load` that define the 206 | spatiotemporal query used to extract data. This typically 207 | includes `measurements`, `x`, `y`, `time`, `resolution`, 208 | `resampling`, `group_by` and `crs`. Keyword arguments can 209 | either be listed directly in the `load_ard` call like any 210 | other parameter (e.g. `measurements=['nbart_red']`), or by 211 | passing in a query kwarg dictionary (e.g. `**query`). For a 212 | list of possible options, see the `dc.load` documentation: 213 | https://datacube-core.readthedocs.io/en/latest/dev/api/generate/datacube.Datacube.load.html 214 | 215 | Returns 216 | ------- 217 | combined_ds : xarray Dataset 218 | An xarray dataset containing only satellite observations that 219 | contains greater than `min_gooddata` proportion of good quality 220 | pixels. 221 | 222 | ''' 223 | 224 | ######### 225 | # Setup # 226 | ######### 227 | # prevent function altering original query object 228 | kwargs = deepcopy(kwargs) 229 | 230 | # We deal with `dask_chunks` separately 231 | dask_chunks = kwargs.pop('dask_chunks', None) 232 | requested_measurements = kwargs.pop('measurements', None) 233 | 234 | # Warn user if they combine lazy load with min_gooddata 235 | if (min_gooddata > 0.0) and dask_chunks is not None: 236 | warnings.warn("Setting 'min_gooddata' percentage to > 0.0 " 237 | "will cause dask arrays to compute when " 238 | "loading pixel-quality data to calculate " 239 | "'good pixel' percentage. This can " 240 | "slow the return of your dataset.") 241 | 242 | # Verify that products were provided and determine if Sentinel-2 243 | # or Landsat data is being loaded 244 | if not products: 245 | raise ValueError(f'Please provide a list of product names ' 246 | f'to load data from.') 247 | 248 | elif all(['level2' in product for product in products]): 249 | product_type = 'c2' 250 | elif all(['sr' in product for product in products]): 251 | product_type = 'c1' 252 | elif all(['s2' in product for product in products]): 253 | product_type = 's2' 254 | elif all(['fractional_cover' in product for product in products]): 255 | product_type = 'fc' 256 | 257 | # If `measurements` are specified but do not include pixel quality bands, 258 | # add these to `measurements` according to collection 259 | if (product_type == 'c2') or (product_type == 'fc'): 260 | print('Using pixel quality parameters for USGS Collection 2') 261 | fmask_band = 'quality_l2_aerosol' 262 | 263 | elif product_type == 'c1': 264 | print('Using pixel quality parameters for USGS Collection 1') 265 | fmask_band = 'pixel_qa' 266 | 267 | elif product_type == 's2': 268 | print('Using pixel quality parameters for Sentinel 2') 269 | fmask_band = 'SCL' 270 | 271 | measurements = requested_measurements.copy() if requested_measurements else None 272 | 273 | # Deal with "load all" case: pick a set of bands common across 274 | # all products 275 | if measurements is None: 276 | if product_type == 'fc': 277 | measurements = ['pv', 'npv', 'bs', 'ue'] 278 | else: 279 | measurements = _common_bands(dc, products) 280 | 281 | # If `measurements` are specified but do not include pq, add. 282 | if measurements: 283 | #pass if FC 284 | if product_type == 'fc': 285 | pass 286 | else: 287 | if fmask_band not in measurements: 288 | measurements.append(fmask_band) 289 | 290 | # Get list of data and mask bands so that we can later exclude 291 | # mask bands from being masked themselves 292 | if product_type == 'fc': 293 | pass 294 | else: 295 | data_bands = [band for band in measurements if band not in (fmask_band)] 296 | mask_bands = [band for band in measurements if band not in data_bands] 297 | 298 | ################# 299 | # Find datasets # 300 | #################l 301 | 302 | # Pull out query params only to pass to dc.find_datasets 303 | query = _dc_query_only(**kwargs) 304 | 305 | # Extract datasets for each product using subset of dcload_kwargs 306 | dataset_list = [] 307 | 308 | # Get list of datasets for each product 309 | print('Finding datasets') 310 | for product in products: 311 | 312 | # Obtain list of datasets for product 313 | print(f' {product}') 314 | datasets = dc.find_datasets(product=product, **query) 315 | 316 | # Remove Landsat 7 SLC-off observations if ls7_slc_off=False 317 | if not ls7_slc_off and product in ['ls7_usgs_sr_scene', 318 | 'usgs_ls7e_level2_2']: 319 | print(' Ignoring SLC-off observations for ls7') 320 | datasets = [i for i in datasets if i.time.begin < 321 | datetime.datetime(2003, 5, 31, tzinfo=pytz.UTC)] 322 | 323 | # Add any returned datasets to list 324 | dataset_list.extend(datasets) 325 | 326 | # Raise exception if no datasets are returned 327 | if len(dataset_list) == 0: 328 | raise ValueError("No data available for query: ensure that " 329 | "the products specified have data for the " 330 | "time and location requested") 331 | 332 | # If pedicate is specified, use this function to filter the list 333 | # of datasets prior to load 334 | if predicate: 335 | print(f'Filtering datasets using filter function') 336 | dataset_list = [ds for ds in dataset_list if predicate(ds)] 337 | 338 | # Raise exception if filtering removes all datasets 339 | if len(dataset_list) == 0: 340 | raise ValueError("No data available after filtering with " 341 | "filter function") 342 | 343 | # load fmask from C2 for masking FC, and filter if required 344 | # NOTE: This works because only one sensor (ls8) has FC, if/when 345 | # FC is calculated for LS7, LS5, will need to move this section 346 | # into the for loop above. 347 | if product_type == 'fc': 348 | 349 | print(' PQ data from USGS C2') 350 | dataset_list_fc_pq = dc.find_datasets(product='usgs_ls8c_level2_2', **query) 351 | 352 | if predicate: 353 | print(f'Filtering datasets using filter function') 354 | dataset_list_fc_pq = [ds for ds in dataset_list_fc_pq if predicate(ds)] 355 | 356 | ############# 357 | # Load data # 358 | ############# 359 | # Note we always load using dask here so that 360 | # we can lazy load data before filtering by good data 361 | ds = dc.load(datasets=dataset_list, 362 | measurements=measurements, 363 | dask_chunks={} if dask_chunks is None else dask_chunks, 364 | **kwargs) 365 | 366 | if product_type == 'fc': 367 | ds_fc_pq = dc.load(datasets=dataset_list_fc_pq, 368 | dask_chunks={} if dask_chunks is None else dask_chunks, 369 | **kwargs) 370 | 371 | #################### 372 | # Filter good data # 373 | #################### 374 | 375 | # need to distinguish between products due to different 376 | # pq band properties 377 | 378 | # collection 2 USGS or FC 379 | if (product_type == 'c2') or (product_type == 'fc'): 380 | if pq_categories_ls is None: 381 | quality_flags_prod = {'cloud_shadow': 'not_cloud_shadow', 382 | 'cloud_or_cirrus': 'not_cloud_or_cirrus', 383 | 'nodata': False} 384 | else: 385 | quality_flags_prod = pq_categories_ls 386 | 387 | if product_type == 'fc': 388 | pq_mask = masking.make_mask(ds_fc_pq[fmask_band], 389 | **quality_flags_prod) 390 | else: 391 | pq_mask = masking.make_mask(ds[fmask_band], 392 | **quality_flags_prod) 393 | 394 | # collection 1 USGS 395 | if product_type == 'c1': 396 | if pq_categories_ls is None: 397 | quality_flags_prod = {'cloud': 'no_cloud', 398 | 'cloud_shadow': 'no_cloud_shadow', 399 | 'nodata': False} 400 | else: 401 | quality_flags_prod = pq_categories_ls 402 | 403 | pq_mask = masking.make_mask(ds[fmask_band], 404 | **quality_flags_prod) 405 | # sentinel 2 406 | if product_type == 's2': 407 | #currently broken for mask band values >=8 408 | #pq_mask = odc.algo.fmask_to_bool(ds[fmask_band], 409 | # categories=pq_categories_s2) 410 | flags_s2 = dc.list_measurements().loc[products[0]].loc[fmask_band]['flags_definition']['qa']['values'] 411 | pq_mask = ds[fmask_band].isin([int(k) for k,v in flags_s2.items() if v in pq_categories_s2]) 412 | 413 | # The good data percentage calculation has to load in all `fmask` 414 | # data, which can be slow. If the user has chosen no filtering 415 | # by using the default `min_gooddata = 0`, we can skip this step 416 | # completely to save processing time 417 | if min_gooddata > 0.0: 418 | 419 | # Compute good data for each observation as % of total pixels 420 | print('Counting good quality pixels for each time step') 421 | data_perc = (pq_mask.sum(axis=[1, 2], dtype='int32') / 422 | (pq_mask.shape[1] * pq_mask.shape[2])) 423 | 424 | keep = data_perc >= min_gooddata 425 | 426 | # Filter by `min_gooddata` to drop low quality observations 427 | total_obs = len(ds.time) 428 | ds = ds.sel(time=keep) 429 | pq_mask = pq_mask.sel(time=keep) 430 | print(f'Filtering to {len(ds.time)} out of {total_obs} ' 431 | f'time steps with at least {min_gooddata:.1%} ' 432 | f'good quality pixels') 433 | 434 | ############### 435 | # Apply masks # 436 | ############### 437 | 438 | # Generate good quality data mask 439 | mask = None 440 | if mask_pixel_quality: 441 | print('Applying pixel quality/cloud mask') 442 | mask = pq_mask 443 | 444 | # Split into data/masks bands, as conversion to float and masking 445 | # should only be applied to data bands 446 | if product_type == 'fc': 447 | ds_data=ds 448 | else: 449 | ds_data = ds[data_bands] 450 | ds_masks = ds[mask_bands] 451 | 452 | # Mask data if either of the above masks were generated 453 | if mask is not None: 454 | ds_data = odc.algo.keep_good_only(ds_data, where=mask) 455 | 456 | # Automatically set dtype to either native or float32 depending 457 | # on whether masking was requested 458 | if dtype == 'auto': 459 | dtype = 'native' if mask is None else 'float32' 460 | 461 | # Set nodata values using odc.algo tools to reduce peak memory 462 | # use when converting data dtype 463 | if dtype != 'native': 464 | ds_data = odc.algo.to_float(ds_data, dtype=dtype) 465 | 466 | # Put data and mask bands back together 467 | if product_type == 'fc': 468 | attrs = ds.attrs 469 | ds = ds_data 470 | ds.attrs.update(attrs) 471 | else: 472 | attrs = ds.attrs 473 | ds = xr.merge([ds_data, ds_masks]) 474 | ds.attrs.update(attrs) 475 | 476 | ############### 477 | # Return data # 478 | ############### 479 | 480 | # Drop bands not originally requested by user 481 | if requested_measurements: 482 | ds = ds[requested_measurements] 483 | 484 | # Scale data 0-1 if requested 485 | if scaling=='normalised': 486 | 487 | if product_type == 'c1': 488 | print("Re-scaling Landsat C1 data") 489 | not_sr_bands = ['pixel_qa','sr_aerosol','radsat_qa'] 490 | 491 | for band in ds.data_vars: 492 | if band not in not_sr_bands: 493 | ds[band]=ds[band]/10000 494 | 495 | if product_type == 's2': 496 | print("Re-scaling Sentinel-2 data") 497 | not_sr_bands = ['scl','qa','mask','water_vapour','aerosol_optical_thickness'] 498 | 499 | for band in ds.data_vars: 500 | if band not in not_sr_bands: 501 | ds[band]=ds[band]/10000 502 | 503 | # Collection 2 Landsat raw values aren't useful so rescale, 504 | # need different factors for surface-temp and SR 505 | if product_type == 'c2': 506 | print("Re-scaling Landsat C2 data") 507 | not_sr_bands = ['thermal_radiance','upwell_radiance','upwell_radiance', 508 | 'atmospheric_transmittance','emissivity','emissivity_stdev', 509 | 'cloud_distance', 'quality_l2_aerosol','quality_l2_surface_temperature', 510 | 'quality_l1_pixel','quality_l1_radiometric_saturation','surface_temperature'] 511 | 512 | for band in ds.data_vars: 513 | 514 | if band == 'surface_temperature': 515 | ds[band]=ds[band]*0.00341802 + 149.0 - 273.15 516 | 517 | if band not in not_sr_bands: 518 | ds[band]=ds[band]* 2.75e-5 - 0.2 519 | 520 | # If user supplied dask_chunks, return data as a dask array without 521 | # actually loading it in 522 | if dask_chunks is not None: 523 | print(f'Returning {len(ds.time)} time steps as a dask array') 524 | return ds 525 | else: 526 | print(f'Loading {len(ds.time)} time steps') 527 | return ds.compute() 528 | 529 | 530 | def array_to_geotiff(fname, data, geo_transform, projection, 531 | nodata_val=0, dtype=gdal.GDT_Float32): 532 | """ 533 | Create a single band GeoTIFF file with data from an array. 534 | 535 | Because this works with simple arrays rather than xarray datasets 536 | from DEA, it requires geotransform info ("(upleft_x, x_size, 537 | x_rotation, upleft_y, y_rotation, y_size)") and projection data 538 | (in "WKT" format) for the output raster. These are typically 539 | obtained from an existing raster using the following GDAL calls: 540 | 541 | import gdal 542 | gdal_dataset = gdal.Open(raster_path) 543 | geotrans = gdal_dataset.GetGeoTransform() 544 | prj = gdal_dataset.GetProjection() 545 | 546 | ...or alternatively, directly from an xarray dataset: 547 | 548 | geotrans = xarraydataset.geobox.transform.to_gdal() 549 | prj = xarraydataset.geobox.crs.wkt 550 | 551 | Parameters 552 | ---------- 553 | fname : str 554 | Output geotiff file path including extension 555 | data : numpy array 556 | Input array to export as a geotiff 557 | geo_transform : tuple 558 | Geotransform for output raster; e.g. "(upleft_x, x_size, 559 | x_rotation, upleft_y, y_rotation, y_size)" 560 | projection : str 561 | Projection for output raster (in "WKT" format) 562 | nodata_val : int, optional 563 | Value to convert to nodata in the output raster; default 0 564 | dtype : gdal dtype object, optional 565 | Optionally set the dtype of the output raster; can be 566 | useful when exporting an array of float or integer values. 567 | Defaults to gdal.GDT_Float32 568 | 569 | """ 570 | 571 | # Set up driver 572 | driver = gdal.GetDriverByName('GTiff') 573 | 574 | # Create raster of given size and projection 575 | rows, cols = data.shape 576 | dataset = driver.Create(fname, cols, rows, 1, dtype) 577 | dataset.SetGeoTransform(geo_transform) 578 | dataset.SetProjection(projection) 579 | 580 | # Write data to array and set nodata values 581 | band = dataset.GetRasterBand(1) 582 | band.WriteArray(data) 583 | band.SetNoDataValue(nodata_val) 584 | 585 | # Close file 586 | dataset = None 587 | 588 | 589 | def mostcommon_crs(dc, product, query): 590 | """ 591 | Takes a given query and returns the most common CRS for observations 592 | returned for that spatial extent. This can be useful when your study 593 | area lies on the boundary of two UTM zones, forcing you to decide 594 | which CRS to use for your `output_crs` in `dc.load`. 595 | 596 | Parameters 597 | ---------- 598 | dc : datacube Datacube object 599 | The Datacube to connect to, i.e. `dc = datacube.Datacube()`. 600 | This allows you to also use development datacubes if required. 601 | product : str 602 | A product name to load CRSs from 603 | query : dict 604 | A datacube query including x, y and time range to assess for the 605 | most common CRS 606 | 607 | Returns 608 | ------- 609 | A EPSG string giving the most common CRS from all datasets returned 610 | by the query above 611 | 612 | """ 613 | 614 | # remove dask_chunks & align to prevent func failing 615 | #prevent function altering dictionary kwargs 616 | query = deepcopy(query) 617 | if 'dask_chunks' in query: 618 | query.pop('dask_chunks', None) 619 | 620 | if 'align' in query: 621 | query.pop('align', None) 622 | 623 | # List of matching products 624 | matching_datasets = dc.find_datasets(product=product, **query) 625 | 626 | # Extract all CRSs 627 | crs_list = [str(i.crs) for i in matching_datasets] 628 | 629 | # Identify most common CRS 630 | crs_counts = Counter(crs_list) 631 | crs_mostcommon = crs_counts.most_common(1)[0][0] 632 | 633 | # Warn user if multiple CRSs are encountered 634 | if len(crs_counts.keys()) > 1: 635 | 636 | warnings.warn(f'Multiple UTM zones {list(crs_counts.keys())} ' 637 | f'were returned for this query. Defaulting to ' 638 | f'the most common zone: {crs_mostcommon}', 639 | UserWarning) 640 | 641 | return crs_mostcommon 642 | 643 | 644 | def download_unzip(url, 645 | output_dir=None, 646 | remove_zip=True): 647 | """ 648 | Downloads and unzips a .zip file from an external URL to a local 649 | directory. 650 | 651 | Parameters 652 | ---------- 653 | url : str 654 | A string giving a URL path to the zip file you wish to download 655 | and unzip 656 | output_dir : str, optional 657 | An optional string giving the directory to unzip files into. 658 | Defaults to None, which will unzip files in the current working 659 | directory 660 | remove_zip : bool, optional 661 | An optional boolean indicating whether to remove the downloaded 662 | .zip file after files are unzipped. Defaults to True, which will 663 | delete the .zip file. 664 | 665 | """ 666 | 667 | # Get basename for zip file 668 | zip_name = os.path.basename(url) 669 | 670 | # Raise exception if the file is not of type .zip 671 | if not zip_name.endswith('.zip'): 672 | raise ValueError(f'The URL provided does not point to a .zip ' 673 | f'file (e.g. {zip_name}). Please specify a ' 674 | f'URL path to a valid .zip file') 675 | 676 | # Download zip file 677 | print(f'Downloading {zip_name}') 678 | r = requests.get(url) 679 | with open(zip_name, 'wb') as f: 680 | f.write(r.content) 681 | 682 | # Extract into output_dir 683 | with zipfile.ZipFile(zip_name, 'r') as zip_ref: 684 | zip_ref.extractall(output_dir) 685 | print(f'Unzipping output files to: ' 686 | f'{output_dir if output_dir else os.getcwd()}') 687 | 688 | # Optionally cleanup 689 | if remove_zip: 690 | os.remove(zip_name) 691 | 692 | 693 | def wofs_fuser(dest, src): 694 | """ 695 | Fuse two WOfS water measurements represented as `ndarray`s. 696 | 697 | Note: this is a copy of the function located here: 698 | https://github.com/GeoscienceAustralia/digitalearthau/blob/develop/digitalearthau/utils.py 699 | """ 700 | empty = (dest & 1).astype(np.bool) 701 | both = ~empty & ~((src & 1).astype(np.bool)) 702 | dest[empty] = src[empty] 703 | dest[both] |= src[both] 704 | 705 | 706 | def dilate(array, dilation=10, invert=True): 707 | """ 708 | Dilate a binary array by a specified nummber of pixels using a 709 | disk-like radial dilation. 710 | 711 | By default, invalid (e.g. False or 0) values are dilated. This is 712 | suitable for applications such as cloud masking (e.g. creating a 713 | buffer around cloudy or shadowed pixels). This functionality can 714 | be reversed by specifying `invert=False`. 715 | 716 | Parameters 717 | ---------- 718 | array : array 719 | The binary array to dilate. 720 | dilation : int, optional 721 | An optional integer specifying the number of pixels to dilate 722 | by. Defaults to 10, which will dilate `array` by 10 pixels. 723 | invert : bool, optional 724 | An optional boolean specifying whether to invert the binary 725 | array prior to dilation. The default is True, which dilates the 726 | invalid values in the array (e.g. False or 0 values). 727 | 728 | Returns 729 | ------- 730 | An array of the same shape as `array`, with valid data pixels 731 | dilated by the number of pixels specified by `dilation`. 732 | """ 733 | 734 | y, x = np.ogrid[ 735 | -dilation : (dilation + 1), 736 | -dilation : (dilation + 1), 737 | ] 738 | 739 | # disk-like radial dilation 740 | kernel = (x * x) + (y * y) <= (dilation + 0.5) ** 2 741 | 742 | # If invert=True, invert True values to False etc 743 | if invert: 744 | array = ~array 745 | 746 | return ~binary_dilation(array.astype(np.bool), 747 | structure=kernel.reshape((1,) + kernel.shape)) 748 | 749 | 750 | def _select_along_axis(values, idx, axis): 751 | other_ind = np.ix_(*[np.arange(s) for s in idx.shape]) 752 | sl = other_ind[:axis] + (idx,) + other_ind[axis:] 753 | return values[sl] 754 | 755 | 756 | def first(array: xr.DataArray, dim: str, index_name: str = None) -> xr.DataArray: 757 | """ 758 | Finds the first occuring non-null value along the given dimension. 759 | 760 | Parameters 761 | ---------- 762 | array : xr.DataArray 763 | The array to search. 764 | dim : str 765 | The name of the dimension to reduce by finding the first non-null value. 766 | 767 | Returns 768 | ------- 769 | reduced : xr.DataArray 770 | An array of the first non-null values. 771 | The `dim` dimension will be removed, and replaced with a coord of the 772 | same name, containing the value of that dimension where the last value 773 | was found. 774 | """ 775 | axis = array.get_axis_num(dim) 776 | idx_first = np.argmax(~pd.isnull(array), axis=axis) 777 | reduced = array.reduce(_select_along_axis, idx=idx_first, axis=axis) 778 | reduced[dim] = array[dim].isel({dim: xr.DataArray(idx_first, dims=reduced.dims)}) 779 | if index_name is not None: 780 | reduced[index_name] = xr.DataArray(idx_first, dims=reduced.dims) 781 | return reduced 782 | 783 | 784 | def last(array: xr.DataArray, dim: str, index_name: str = None) -> xr.DataArray: 785 | """ 786 | Finds the last occuring non-null value along the given dimension. 787 | 788 | Parameters 789 | ---------- 790 | array : xr.DataArray 791 | The array to search. 792 | dim : str 793 | The name of the dimension to reduce by finding the last non-null value. 794 | index_name : str, optional 795 | If given, the name of a coordinate to be added containing the index 796 | of where on the dimension the nearest value was found. 797 | 798 | Returns 799 | ------- 800 | reduced : xr.DataArray 801 | An array of the last non-null values. 802 | The `dim` dimension will be removed, and replaced with a coord of the 803 | same name, containing the value of that dimension where the last value 804 | was found. 805 | """ 806 | axis = array.get_axis_num(dim) 807 | rev = (slice(None),) * axis + (slice(None, None, -1),) 808 | idx_last = -1 - np.argmax(~pd.isnull(array)[rev], axis=axis) 809 | reduced = array.reduce(_select_along_axis, idx=idx_last, axis=axis) 810 | reduced[dim] = array[dim].isel({dim: xr.DataArray(idx_last, dims=reduced.dims)}) 811 | if index_name is not None: 812 | reduced[index_name] = xr.DataArray(idx_last, dims=reduced.dims) 813 | return reduced 814 | 815 | 816 | def nearest(array: xr.DataArray, dim: str, target, index_name: str = None) -> xr.DataArray: 817 | """ 818 | Finds the nearest values to a target label along the given dimension, for 819 | all other dimensions. 820 | 821 | E.g. For a DataArray with dimensions ('time', 'x', 'y') 822 | 823 | nearest_array = nearest(array, 'time', '2017-03-12') 824 | 825 | will return an array with the dimensions ('x', 'y'), with non-null values 826 | found closest for each (x, y) pixel to that location along the time 827 | dimension. 828 | 829 | The returned array will include the 'time' coordinate for each x,y pixel 830 | that the nearest value was found. 831 | 832 | Parameters 833 | ---------- 834 | array : xr.DataArray 835 | The array to search. 836 | dim : str 837 | The name of the dimension to look for the target label. 838 | target : same type as array[dim] 839 | The value to look up along the given dimension. 840 | index_name : str, optional 841 | If given, the name of a coordinate to be added containing the index 842 | of where on the dimension the nearest value was found. 843 | 844 | Returns 845 | ------- 846 | nearest_array : xr.DataArray 847 | An array of the nearest non-null values to the target label. 848 | The `dim` dimension will be removed, and replaced with a coord of the 849 | same name, containing the value of that dimension closest to the 850 | given target label. 851 | """ 852 | before_target = slice(None, target) 853 | after_target = slice(target, None) 854 | 855 | da_before = array.sel({dim: before_target}) 856 | da_after = array.sel({dim: after_target}) 857 | 858 | da_before = last(da_before, dim, index_name) if da_before[dim].shape[0] else None 859 | da_after = first(da_after, dim, index_name) if da_after[dim].shape[0] else None 860 | 861 | if da_before is None and da_after is not None: 862 | return da_after 863 | if da_after is None and da_before is not None: 864 | return da_before 865 | 866 | target = array[dim].dtype.type(target) 867 | is_before_closer = abs(target - da_before[dim]) < abs(target - da_after[dim]) 868 | nearest_array = xr.where(is_before_closer, da_before, da_after) 869 | nearest_array[dim] = xr.where(is_before_closer, da_before[dim], da_after[dim]) 870 | if index_name is not None: 871 | nearest_array[index_name] = xr.where(is_before_closer, 872 | da_before[index_name], 873 | da_after[index_name]) 874 | return nearest_array 875 | 876 | -------------------------------------------------------------------------------- /parameters.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "ParameterKey": "KeyName", 4 | "ParameterValue": "odckey" 5 | }, 6 | { 7 | "ParameterKey": "InstanceType", 8 | "ParameterValue": "t2.small" 9 | }, 10 | { 11 | "ParameterKey": "ExtentToIndex", 12 | "ParameterValue": "146.30,146.83,-43.54,-43.20" 13 | }, 14 | { 15 | "ParameterKey": "SecretPassword", 16 | "ParameterValue": "anothersecretpassword123" 17 | }, 18 | { 19 | "ParameterKey": "Region", 20 | "ParameterValue": "eu-west-1" 21 | } 22 | ] 23 | 24 | -------------------------------------------------------------------------------- /products.csv: -------------------------------------------------------------------------------- 1 | product,definition 2 | s2_l2a,https://raw.githubusercontent.com/digitalearthafrica/config/master/products/esa_s2_l2a.odc-product.yaml 3 | io_lulc,https://raw.githubusercontent.com/opendatacube/datacube-dataset-config/master/products/io_lulc.odc-product.yaml 4 | nasadem,https://raw.githubusercontent.com/opendatacube/datacube-dataset-config/master/products/nasadem.odc-product.yaml 5 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | --extra-index-url https://packages.dea.ga.gov.au/ 2 | aiohttp 3 | aiobotocore[boto3,awscli]==1.0.7 4 | pyyaml 5 | wget 6 | requests 7 | datacube 8 | odc-stac 9 | odc-stats 10 | odc-dscache 11 | odc-cloud[ASYNC] 12 | odc-ui 13 | odc-io 14 | odc-apps-dc-tools 15 | dask==2023.10.1 16 | distributed==2023.10.1 17 | # Jupyter components. 18 | gdal 19 | scipy 20 | jupyter==1.0.0 21 | jupyterlab==3.2.0 22 | ipyleaflet 23 | folium 24 | pandas==1.3.4 25 | numpy 26 | xarray==0.16.0 27 | matplotlib 28 | geopandas 29 | scikit-image 30 | tqdm 31 | click<8.1.0 32 | python-dateutil==2.7.5 33 | sat-search 34 | 35 | # for tests 36 | pytest 37 | testbook 38 | -------------------------------------------------------------------------------- /scripts/install-docker.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | install_docker_ubuntu() { 4 | # Copied from : https://docs.docker.com/engine/install/ubuntu/ 5 | sudo apt-get remove -y docker docker-engine docker.io containerd runc 6 | 7 | sudo apt-get update 8 | sudo DEBIAN_FRONTEND=noninteractive apt-get install -y apt-transport-https ca-certificates curl gnupg lsb-release 9 | 10 | curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg 11 | 12 | echo "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null 13 | 14 | sudo apt-get update 15 | sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-compose 16 | 17 | sudo service docker start 18 | } 19 | 20 | install_docker_fedora() { 21 | # Copied from: https://docs.docker.com/engine/install/fedora/ 22 | sudo dnf remove -y docker \ 23 | docker-client \ 24 | docker-client-latest \ 25 | docker-common \ 26 | docker-latest \ 27 | docker-latest-logrotate \ 28 | docker-logrotate \ 29 | docker-selinux \ 30 | docker-engine-selinux \ 31 | docker-engine 32 | sudo dnf -y install dnf-plugins-core 33 | 34 | sudo dnf config-manager --add-repo https://download.docker.com/linux/fedora/docker-ce.repo 35 | 36 | sudo dnf install -y docker-ce docker-ce-cli containerd.io docker-compose 37 | sudo systemctl enable docker.service 38 | sudo systemctl enable containerd.service 39 | sudo systemctl start docker 40 | } 41 | 42 | install_docker_macos() { 43 | curl https://desktop.docker.com/mac/stable/amd64/Docker.dmg?utm_source=docker&utm_medium=webreferral&utm_campaign=docs-driven-download-mac-amd64 -O ~/Downloads/Docker.dmg 44 | hdiutil attach ~/Downloads/Docker.dmg 45 | echo "Install the Docker from the new Drive that is on the Desktop..." 46 | read -p "Press enter to continue... " 47 | } 48 | 49 | case "$OSTYPE" in 50 | linux-gnu) 51 | osname="$(grep '^ID=' /etc/os-release | cut -d'=' -f2)" 52 | case "$osname" in 53 | fedora) install_docker_fedora ;; 54 | ubuntu) install_docker_ubuntu ;; 55 | *) echo "We currently do not support your OS" ;; 56 | esac 57 | ;; 58 | darwun*) install_docker_macos ;; 59 | *) echo "We currently do not support your OS" ;; 60 | esac 61 | -------------------------------------------------------------------------------- /scripts/install-system-requirements.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if hash apt 2>/dev/null; then 4 | sudo apt install -y make 5 | exit 6 | fi 7 | 8 | if hash dnf 2>/dev/null; then 9 | sudo dnf install -y make 10 | exit 11 | fi 12 | 13 | if hash yum 2>/dev/null; then 14 | sudo yum install -y make 15 | exit 16 | fi 17 | 18 | if hash brew 2>/dev/null; then 19 | brew install make 20 | exit 21 | fi 22 | -------------------------------------------------------------------------------- /setup.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | base="$(dirname "$0")" 3 | bash "$base/scripts/install-system-requirements.sh" 4 | bash "$base/scripts/install-docker.sh" 5 | -------------------------------------------------------------------------------- /tests/test_notebooks.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | from testbook import testbook 3 | import pytest 4 | 5 | TEST_DIR = Path(__file__).parent.parent.resolve() 6 | NB_DIR = TEST_DIR.parent 7 | 8 | @pytest.mark.skip() 9 | @testbook(f'{NB_DIR}/notebooks/Indexing_More_Data.ipynb', execute=True, timeout=180) 10 | def test_indexing_more_data(tb): 11 | assert True # ok 12 | 13 | @testbook(f'{NB_DIR}/notebooks/ESRI_Land_Cover.ipynb', execute=True, timeout=180) 14 | def test_esri_land_cover(tb): 15 | assert True # ok 16 | 17 | @testbook(f'{NB_DIR}/notebooks/NASADEM.ipynb', execute=True, timeout=180) 18 | def test_nasadem(tb): 19 | assert True # ok 20 | 21 | @testbook(f'{NB_DIR}/notebooks/Sentinel_2.ipynb',timeout=180) 22 | def test_sentinel_2(tb): 23 | tb.execute_cell(1) 24 | tb.inject( 25 | """ 26 | import sys 27 | sys.path.insert(1, '/notebooks/') 28 | """ 29 | ) 30 | tb.execute_cell(slice(3, 10)) 31 | assert True # ok --------------------------------------------------------------------------------