Introduzione Data Wrangling di Immagini Geospaziali 🛰️ 🌍

In questo tutorial vedremo come poter accedere ai dati del satellite Sentinel-2. Vedremo alcune librerie comuni utilizzate nel Geospatial Data Science ed il loro utilizzo per accere ai dati utili al progetto Copernicus.

In particolare vedremo:

  1. Come generare un footprint (ossia come definire l'area di studio)
  2. Come interrogare il database di delle immagini delle missioni Sentinel (ossia il Copernicus Open Access Hub)
  3. Come scaricare ed estrarre i file interessanti
  4. Come leggere, scrivere ed infine manipolare le immagini riprese.

Tutorial by Francesco Pelosin

Installazione prerequisiti

In [0]:
!apt install ffmpeg
!pip install geopandas
!pip install folium
!pip install sentinelsat
!pip install rasterio
!pip install opencv-python
!pip install imageio
Reading package lists... Done
Building dependency tree       
Reading state information... Done
ffmpeg is already the newest version (7:3.4.6-0ubuntu0.18.04.1).
The following package was automatically installed and is no longer required:
  libnvidia-common-430
Use 'apt autoremove' to remove it.
0 upgraded, 0 newly installed, 0 to remove and 7 not upgraded.
Collecting geopandas
  Downloading https://files.pythonhosted.org/packages/5b/0c/e6c99e561b03482220f00443f610ccf4dce9b50f4b1093d735f93c6fc8c6/geopandas-0.6.2-py2.py3-none-any.whl (919kB)
     |████████████████████████████████| 921kB 4.8MB/s 
Collecting fiona
  Downloading https://files.pythonhosted.org/packages/50/f7/9899f8a9a2e38601472fe1079ce5088f58833221c8b8507d8b5eafd5404a/Fiona-1.8.13-cp36-cp36m-manylinux1_x86_64.whl (11.8MB)
     |████████████████████████████████| 11.8MB 43.9MB/s 
Requirement already satisfied: shapely in /usr/local/lib/python3.6/dist-packages (from geopandas) (1.6.4.post2)
Collecting pyproj
  Downloading https://files.pythonhosted.org/packages/d6/70/eedc98cd52b86de24a1589c762612a98bea26cde649ffdd60c1db396cce8/pyproj-2.4.2.post1-cp36-cp36m-manylinux2010_x86_64.whl (10.1MB)
     |████████████████████████████████| 10.1MB 33.0MB/s 
Requirement already satisfied: pandas>=0.23.0 in /usr/local/lib/python3.6/dist-packages (from geopandas) (0.25.3)
Collecting click-plugins>=1.0
  Downloading https://files.pythonhosted.org/packages/e9/da/824b92d9942f4e472702488857914bdd50f73021efea15b4cad9aca8ecef/click_plugins-1.1.1-py2.py3-none-any.whl
Collecting cligj>=0.5
  Downloading https://files.pythonhosted.org/packages/e4/be/30a58b4b0733850280d01f8bd132591b4668ed5c7046761098d665ac2174/cligj-0.5.0-py3-none-any.whl
Collecting munch
  Downloading https://files.pythonhosted.org/packages/cc/ab/85d8da5c9a45e072301beb37ad7f833cd344e04c817d97e0cc75681d248f/munch-2.5.0-py2.py3-none-any.whl
Requirement already satisfied: click<8,>=4.0 in /usr/local/lib/python3.6/dist-packages (from fiona->geopandas) (7.0)
Requirement already satisfied: attrs>=17 in /usr/local/lib/python3.6/dist-packages (from fiona->geopandas) (19.3.0)
Requirement already satisfied: six>=1.7 in /usr/local/lib/python3.6/dist-packages (from fiona->geopandas) (1.12.0)
Requirement already satisfied: numpy>=1.13.3 in /usr/local/lib/python3.6/dist-packages (from pandas>=0.23.0->geopandas) (1.17.4)
Requirement already satisfied: pytz>=2017.2 in /usr/local/lib/python3.6/dist-packages (from pandas>=0.23.0->geopandas) (2018.9)
Requirement already satisfied: python-dateutil>=2.6.1 in /usr/local/lib/python3.6/dist-packages (from pandas>=0.23.0->geopandas) (2.6.1)
Installing collected packages: click-plugins, cligj, munch, fiona, pyproj, geopandas
Successfully installed click-plugins-1.1.1 cligj-0.5.0 fiona-1.8.13 geopandas-0.6.2 munch-2.5.0 pyproj-2.4.2.post1
Requirement already satisfied: folium in /usr/local/lib/python3.6/dist-packages (0.8.3)
Requirement already satisfied: requests in /usr/local/lib/python3.6/dist-packages (from folium) (2.21.0)
Requirement already satisfied: six in /usr/local/lib/python3.6/dist-packages (from folium) (1.12.0)
Requirement already satisfied: numpy in /usr/local/lib/python3.6/dist-packages (from folium) (1.17.4)
Requirement already satisfied: branca>=0.3.0 in /usr/local/lib/python3.6/dist-packages (from folium) (0.3.1)
Requirement already satisfied: jinja2 in /usr/local/lib/python3.6/dist-packages (from folium) (2.10.3)
Requirement already satisfied: certifi>=2017.4.17 in /usr/local/lib/python3.6/dist-packages (from requests->folium) (2019.11.28)
Requirement already satisfied: urllib3<1.25,>=1.21.1 in /usr/local/lib/python3.6/dist-packages (from requests->folium) (1.24.3)
Requirement already satisfied: idna<2.9,>=2.5 in /usr/local/lib/python3.6/dist-packages (from requests->folium) (2.8)
Requirement already satisfied: chardet<3.1.0,>=3.0.2 in /usr/local/lib/python3.6/dist-packages (from requests->folium) (3.0.4)
Requirement already satisfied: MarkupSafe>=0.23 in /usr/local/lib/python3.6/dist-packages (from jinja2->folium) (1.1.1)
Collecting sentinelsat
  Downloading https://files.pythonhosted.org/packages/5c/79/c2ac7b71dd13db95a9b83865bbbc7f1e4359c2b141bedad21b0e181fa06e/sentinelsat-0.13-py2.py3-none-any.whl
Requirement already satisfied: six in /usr/local/lib/python3.6/dist-packages (from sentinelsat) (1.12.0)
Requirement already satisfied: requests in /usr/local/lib/python3.6/dist-packages (from sentinelsat) (2.21.0)
Requirement already satisfied: tqdm in /usr/local/lib/python3.6/dist-packages (from sentinelsat) (4.28.1)
Collecting html2text
  Downloading https://files.pythonhosted.org/packages/49/21/eb38d335ab15fc13564a5e971c1403707fb3a037292f246fa82e17208794/html2text-2019.9.26-py3-none-any.whl
Collecting geomet
  Downloading https://files.pythonhosted.org/packages/d8/c4/da43311a7b610466fa6ab491fe63bad0535889df8108f2dc53ebe3198494/geomet-0.2.1-py3-none-any.whl
Collecting geojson>=2
  Downloading https://files.pythonhosted.org/packages/e4/8d/9e28e9af95739e6d2d2f8d4bef0b3432da40b7c3588fbad4298c1be09e48/geojson-2.5.0-py2.py3-none-any.whl
Requirement already satisfied: click in /usr/local/lib/python3.6/dist-packages (from sentinelsat) (7.0)
Requirement already satisfied: urllib3<1.25,>=1.21.1 in /usr/local/lib/python3.6/dist-packages (from requests->sentinelsat) (1.24.3)
Requirement already satisfied: certifi>=2017.4.17 in /usr/local/lib/python3.6/dist-packages (from requests->sentinelsat) (2019.11.28)
Requirement already satisfied: chardet<3.1.0,>=3.0.2 in /usr/local/lib/python3.6/dist-packages (from requests->sentinelsat) (3.0.4)
Requirement already satisfied: idna<2.9,>=2.5 in /usr/local/lib/python3.6/dist-packages (from requests->sentinelsat) (2.8)
Installing collected packages: html2text, geomet, geojson, sentinelsat
Successfully installed geojson-2.5.0 geomet-0.2.1 html2text-2019.9.26 sentinelsat-0.13
Collecting rasterio
  Downloading https://files.pythonhosted.org/packages/be/e5/7052a3eef72af7e883a280d8dff64f4ea44cb92ec25ffb1d00ce27bc1a12/rasterio-1.1.2-cp36-cp36m-manylinux1_x86_64.whl (18.0MB)
     |████████████████████████████████| 18.0MB 246kB/s 
Requirement already satisfied: click-plugins in /usr/local/lib/python3.6/dist-packages (from rasterio) (1.1.1)
Requirement already satisfied: cligj>=0.5 in /usr/local/lib/python3.6/dist-packages (from rasterio) (0.5.0)
Requirement already satisfied: numpy in /usr/local/lib/python3.6/dist-packages (from rasterio) (1.17.4)
Requirement already satisfied: attrs in /usr/local/lib/python3.6/dist-packages (from rasterio) (19.3.0)
Requirement already satisfied: click<8,>=4.0 in /usr/local/lib/python3.6/dist-packages (from rasterio) (7.0)
Collecting affine
  Downloading https://files.pythonhosted.org/packages/ac/a6/1a39a1ede71210e3ddaf623982b06ecfc5c5c03741ae659073159184cd3e/affine-2.3.0-py2.py3-none-any.whl
Collecting snuggs>=1.4.1
  Downloading https://files.pythonhosted.org/packages/cc/0e/d27d6e806d6c0d1a2cfdc5d1f088e42339a0a54a09c3343f7f81ec8947ea/snuggs-1.4.7-py3-none-any.whl
Requirement already satisfied: pyparsing>=2.1.6 in /usr/local/lib/python3.6/dist-packages (from snuggs>=1.4.1->rasterio) (2.4.5)
Installing collected packages: affine, snuggs, rasterio
Successfully installed affine-2.3.0 rasterio-1.1.2 snuggs-1.4.7
Requirement already satisfied: opencv-python in /usr/local/lib/python3.6/dist-packages (4.1.2.30)
Requirement already satisfied: numpy>=1.11.3 in /usr/local/lib/python3.6/dist-packages (from opencv-python) (1.17.4)
Requirement already satisfied: imageio in /usr/local/lib/python3.6/dist-packages (2.4.1)
Requirement already satisfied: numpy in /usr/local/lib/python3.6/dist-packages (from imageio) (1.17.4)
Requirement already satisfied: pillow in /usr/local/lib/python3.6/dist-packages (from imageio) (4.3.0)
Requirement already satisfied: olefile in /usr/local/lib/python3.6/dist-packages (from pillow->imageio) (0.46)
In [0]:
# Librerie specifiche per lavorare con dati Geospaziali
import geopandas as gpd
import folium
from sentinelsat import SentinelAPI, read_geojson, geojson_to_wkt 

# Librerie specifiche per lavorare con dati immagine
import rasterio as rio
import rasterio.mask
from rasterio.plot import show
import matplotlib.pyplot as plt
import imageio
import cv2

# Libreria calcolo vettoriale
import numpy as np

# Librerie di aiuto per l'interfaccia OS
import zipfile
import os
import base64
from IPython.display import HTML
In [0]:
# Preparazione delle cartelle necessarie
if not os.path.exists("./data/"):
    os.makedirs("./data/")
if not os.path.exists("./video/"):
    os.makedirs("./video/")
if not os.path.exists("./imgs/"):
    os.makedirs("./imgs/")
if not os.path.exists("./geoj/"):
    os.makedirs("./geoj/")

def rim_info(rim):
    print(f"Number of bands: {rim.count}")
    print(f"Dset bounds: {rim.bounds}")
    print(f"{(rim.bounds.top - rim.bounds.bottom)/ 1000} km² high")
    print(f"{(rim.bounds.right - rim.bounds.left)/ 1000} km² wide")

def playvideo(filename):
    video = open(filename, 'r+b').read()
    encoded = base64.b64encode(video)
    return HTML(data='''<video alt="test" controls><source src="data:video/mp4;base64,{0}" type="video/mp4"/></video>'''.format(encoded.decode('ascii')))

    
def get_bands_files(R_dir):    
    
    bands_dict = {}
    
    for x in os.listdir(R_dir):
        if ('_B08_' in x):
            bands_dict['B08'] = R_dir+x
        elif ('_B12_' in x):
            bands_dict['B12'] = R_dir+x
        elif ('_B11_' in x):
            bands_dict['B11'] = R_dir+x
        elif ('_B07_' in x):
            bands_dict['B07'] = R_dir+x
        elif ('_B06_' in x):
            bands_dict['B06'] = R_dir+x
        elif ('_B05_' in x):
            bands_dict['B05'] = R_dir+x
        elif ('_B04_' in x):
            bands_dict['B04'] = R_dir+x
        elif ('_B03_' in x):
            bands_dict['B03'] = R_dir+x
        elif ('_B02_' in x):
            bands_dict['B02'] = R_dir+x
        else:
            pass

    return bands_dict

Selezione dell'area di studio (footprint)

  • Identifico l'area di studio attraverso un .geojson nello specifico utilizziamo questo servizio online http://geojson.io/
  • Cosa sono i file .geojson?
  • Cos'è il formato wkt?
In [0]:
%%writefile ./geoj/venezia.geojson
{
  "type": "FeatureCollection",
  "features": [
    {
      "type": "Feature",
      "properties": {},
      "geometry": {
        "type": "Polygon",
        "coordinates": [
          [
            [
              12.316017150878906,
              45.46807675634549
            ],
            [
              12.284774780273438,
              45.4324308667588
            ],
            [
              12.288551330566406,
              45.340080499681875
            ],
            [
              12.344512939453125,
              45.33284041773058
            ],
            [
              12.41455078125,
              45.44471679159555
            ],
            [
              12.441329956054688,
              45.47433654565603
            ],
            [
              12.316017150878906,
              45.46807675634549
            ]
          ]
        ]
      }
    }
  ]
}
Writing ./geoj/venezia.geojson
In [0]:
# Selezione del nostro .geojson file
GEOJSON_FILE = './geoj/venezia.geojson'

# Generazione del poligono in formato wkt
footprint = geojson_to_wkt(read_geojson(GEOJSON_FILE))
print(f"Stringa identificativa del poligono disegnato in formato wtk:\n{footprint}")

# Visualizzazione del poligono
bounds = folium.GeoJson(GEOJSON_FILE).get_bounds() 
c_lat = (bounds[0][0] + bounds[1][0])/2
c_lon = (bounds[0][1] + bounds[1][1])/2
m = folium.Map([c_lat,c_lon], tiles='Stamen Terrain')
folium.GeoJson(GEOJSON_FILE).add_to(m)

m
Stringa identificativa del poligono disegnato in formato wtk:
POLYGON((12.3160 45.4681,12.2848 45.4324,12.2886 45.3401,12.3445 45.3328,12.4146 45.4447,12.4413 45.4743,12.3160 45.4681))
Out[0]:

Sentinelsat API

Credenziali

In [0]:
# Credenziali
USER = 'YOURUSR'
PSW = 'YOURPSW'

Inizializzazione API

In [0]:
# Inizializzazione oggetto API
api = SentinelAPI(USER, PSW, 'https://scihub.copernicus.eu/dhus')

# Parametri query
INIT_DATE = '20190101'
END_DATE = '20191115'
PLATFORM_NAME = 'Sentinel-2'
PROCESSING_LEVEL = 'Level-2A'

# Ricerca tramite area di studio
products = api.query(footprint,
                     date = (INIT_DATE, END_DATE),
                     platformname=PLATFORM_NAME,
                     processinglevel = PROCESSING_LEVEL,
                     cloudcoverpercentage = (0,90))
print(f"{len(products)} prodotti trovati.")
Querying products: 100%|██████████| 197/197 [00:01<00:00, 52.16 products/s]
197 prodotti trovati.

Conversione dei risultati in formato Geodataframe

  • Cos'è un Geodataframe?
In [0]:
products_gdf = api.to_geodataframe(products)
products_gdf_sorted = products_gdf.sort_values(['cloudcoverpercentage'], ascending=[True])
print(products_gdf_sorted['cloudcoverpercentage'])
products_gdf_sorted
7d5a2b8d-c54a-41fe-8bbd-4c11ccf85583     0.113177
6db6191b-027d-456d-96fa-9d57178100c3     0.131045
b83bd2bf-ee8b-45ca-a8ec-0c43635be2de     0.142274
9e4073ae-201c-4c0e-9b83-7c48323e63af     0.145452
f8ce8bf4-1d81-4e70-8a4d-b0bcce7430f3     0.145875
                                          ...    
39481b9b-983e-4a87-8c4d-e42428fa2828    88.701160
aa6f66b6-5b0b-4ee8-bd2d-f74f5cf81287    89.633872
0eeae2ef-24f1-4c19-8f98-403252775992    89.696455
c3694ba9-2f11-40ea-9537-051bf9bb2552    89.804619
411745d3-fbb7-445d-bdba-91fa75286984    89.872329
Name: cloudcoverpercentage, Length: 197, dtype: float64
Out[0]:
title link link_alternative link_icon summary beginposition endposition ingestiondate orbitnumber relativeorbitnumber cloudcoverpercentage highprobacloudspercentage mediumprobacloudspercentage snowicepercentage vegetationpercentage waterpercentage notvegetatedpercentage unclassifiedpercentage format instrumentshortname instrumentname s2datatakeid platformidentifier orbitdirection platformserialidentifier processingbaseline processinglevel producttype platformname size filename level1cpdiidentifier identifier uuid geometry
7d5a2b8d-c54a-41fe-8bbd-4c11ccf85583 S2B_MSIL2A_20190628T101039_N0212_R022_T33TUL_2... https://scihub.copernicus.eu/dhus/odata/v1/Pro... https://scihub.copernicus.eu/dhus/odata/v1/Pro... https://scihub.copernicus.eu/dhus/odata/v1/Pro... Date: 2019-06-28T10:10:39.024Z, Instrument: MS... 2019-06-28 10:10:39.024 2019-06-28 10:10:39.024 2019-06-29 06:49:52.422 12060 22 0.113177 0.050634 0.059140 0.000026 29.085377 55.206275 15.085511 0.273470 SAFE MSI Multi-Spectral Instrument GS2B_20190628T101039_012060_N02.12 2017-013A DESCENDING Sentinel-2B 02.12 Level-2A S2MSI2A Sentinel-2 952.30 MB S2B_MSIL2A_20190628T101039_N0212_R022_T33TUL_2... S2B_OPER_MSI_L1C_TL_EPAE_20190628T134555_A0120... S2B_MSIL2A_20190628T101039_N0212_R022_T33TUL_2... 7d5a2b8d-c54a-41fe-8bbd-4c11ccf85583 MULTIPOLYGON (((12.46079 45.03702, 13.51054 45...
6db6191b-027d-456d-96fa-9d57178100c3 S2B_MSIL2A_20191026T101029_N0213_R022_T32TQR_2... https://scihub.copernicus.eu/dhus/odata/v1/Pro... https://scihub.copernicus.eu/dhus/odata/v1/Pro... https://scihub.copernicus.eu/dhus/odata/v1/Pro... Date: 2019-10-26T10:10:29.024Z, Instrument: MS... 2019-10-26 10:10:29.024 2019-10-26 10:10:29.024 2019-10-26 20:10:00.902 13776 22 0.131045 0.064635 0.064578 0.000156 35.929367 26.902407 29.680073 3.767464 SAFE MSI Multi-Spectral Instrument GS2B_20191026T101029_013776_N02.13 2017-013A DESCENDING Sentinel-2B 02.13 Level-2A S2MSI2A Sentinel-2 1.05 GB S2B_MSIL2A_20191026T101029_N0213_R022_T32TQR_2... S2B_OPER_MSI_L1C_TL_MPS__20191026T122625_A0137... S2B_MSIL2A_20191026T101029_N0213_R022_T32TQR_2... 6db6191b-027d-456d-96fa-9d57178100c3 MULTIPOLYGON (((12.93003 44.99759, 12.99932 45...
b83bd2bf-ee8b-45ca-a8ec-0c43635be2de S2A_MSIL2A_20190114T101351_N0211_R022_T33TUL_2... https://scihub.copernicus.eu/dhus/odata/v1/Pro... https://scihub.copernicus.eu/dhus/odata/v1/Pro... https://scihub.copernicus.eu/dhus/odata/v1/Pro... Date: 2019-01-14T10:13:51.024Z, Instrument: MS... 2019-01-14 10:13:51.024 2019-01-14 10:13:51.024 2019-01-14 20:09:46.646 18609 22 0.142274 0.045427 0.090002 0.006662 10.416094 55.362308 18.290167 9.357703 SAFE MSI Multi-Spectral Instrument GS2A_20190114T101351_018609_N02.11 2015-028A DESCENDING Sentinel-2A 02.11 Level-2A S2MSI2A Sentinel-2 873.89 MB S2A_MSIL2A_20190114T101351_N0211_R022_T33TUL_2... NaN S2A_MSIL2A_20190114T101351_N0211_R022_T33TUL_2... b83bd2bf-ee8b-45ca-a8ec-0c43635be2de POLYGON ((13.83869 45.82391, 13.81386 45.76484...
9e4073ae-201c-4c0e-9b83-7c48323e63af S2A_MSIL2A_20190401T100031_N0211_R122_T33TUL_2... https://scihub.copernicus.eu/dhus/odata/v1/Pro... https://scihub.copernicus.eu/dhus/odata/v1/Pro... https://scihub.copernicus.eu/dhus/odata/v1/Pro... Date: 2019-04-01T10:00:31.024Z, Instrument: MS... 2019-04-01 10:00:31.024 2019-04-01 10:00:31.024 2019-04-01 23:53:15.602 19710 122 0.145452 0.062653 0.080739 0.000040 22.991164 51.197332 24.381883 0.784877 SAFE MSI Multi-Spectral Instrument GS2A_20190401T100031_019710_N02.11 2015-028A DESCENDING Sentinel-2A 02.11 Level-2A S2MSI2A Sentinel-2 929.69 MB S2A_MSIL2A_20190401T100031_N0211_R122_T33TUL_2... S2A_OPER_MSI_L1C_TL_EPAE_20190401T105727_A0197... S2A_MSIL2A_20190401T100031_N0211_R122_T33TUL_2... 9e4073ae-201c-4c0e-9b83-7c48323e63af MULTIPOLYGON (((12.46079 45.03702, 13.85437 45...
f8ce8bf4-1d81-4e70-8a4d-b0bcce7430f3 S2A_MSIL2A_20190921T101031_N0213_R022_T33TUL_2... https://scihub.copernicus.eu/dhus/odata/v1/Pro... https://scihub.copernicus.eu/dhus/odata/v1/Pro... https://scihub.copernicus.eu/dhus/odata/v1/Pro... Date: 2019-09-21T10:10:31.024Z, Instrument: MS... 2019-09-21 10:10:31.024 2019-09-21 10:10:31.024 2019-09-21 18:39:25.293 22184 22 0.145875 0.075595 0.066192 0.000099 34.952787 55.193174 9.099825 0.311017 SAFE MSI Multi-Spectral Instrument GS2A_20190921T101031_022184_N02.13 2015-028A DESCENDING Sentinel-2A 02.13 Level-2A S2MSI2A Sentinel-2 841.42 MB S2A_MSIL2A_20190921T101031_N0213_R022_T33TUL_2... S2A_OPER_MSI_L1C_TL_SGS__20190921T121457_A0221... S2A_MSIL2A_20190921T101031_N0213_R022_T33TUL_2... f8ce8bf4-1d81-4e70-8a4d-b0bcce7430f3 MULTIPOLYGON (((12.46079 45.03702, 13.51743 45...
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
39481b9b-983e-4a87-8c4d-e42428fa2828 S2A_MSIL2A_20190404T101031_N0211_R022_T33TUL_2... https://scihub.copernicus.eu/dhus/odata/v1/Pro... https://scihub.copernicus.eu/dhus/odata/v1/Pro... https://scihub.copernicus.eu/dhus/odata/v1/Pro... Date: 2019-04-04T10:10:31.024Z, Instrument: MS... 2019-04-04 10:10:31.024 2019-04-04 10:10:31.024 2019-04-05 05:44:18.718 19753 22 88.701160 74.466211 9.008597 1.494092 0.662528 6.871126 0.647108 1.305111 SAFE MSI Multi-Spectral Instrument GS2A_20190404T101031_019753_N02.11 2015-028A DESCENDING Sentinel-2A 02.11 Level-2A S2MSI2A Sentinel-2 807.60 MB S2A_MSIL2A_20190404T101031_N0211_R022_T33TUL_2... S2A_OPER_MSI_L1C_TL_SGS__20190404T185546_A0197... S2A_MSIL2A_20190404T101031_N0211_R022_T33TUL_2... 39481b9b-983e-4a87-8c4d-e42428fa2828 MULTIPOLYGON (((12.46079 45.03702, 13.52154 45...
aa6f66b6-5b0b-4ee8-bd2d-f74f5cf81287 S2B_MSIL2A_20190708T101039_N0213_R022_T32TQR_2... https://scihub.copernicus.eu/dhus/odata/v1/Pro... https://scihub.copernicus.eu/dhus/odata/v1/Pro... https://scihub.copernicus.eu/dhus/odata/v1/Pro... Date: 2019-07-08T10:10:39.024Z, Instrument: MS... 2019-07-08 10:10:39.024 2019-07-08 10:10:39.024 2019-07-09 05:31:24.272 12203 22 89.633872 20.556690 36.318547 0.000999 5.601345 1.734682 2.592287 0.370125 SAFE MSI Multi-Spectral Instrument GS2B_20190708T101039_012203_N02.13 2017-013A DESCENDING Sentinel-2B 02.13 Level-2A S2MSI2A Sentinel-2 949.75 MB S2B_MSIL2A_20190708T101039_N0213_R022_T32TQR_2... S2B_OPER_MSI_L1C_TL_EPAE_20190708T125653_A0122... S2B_MSIL2A_20190708T101039_N0213_R022_T32TQR_2... aa6f66b6-5b0b-4ee8-bd2d-f74f5cf81287 MULTIPOLYGON (((12.93003 44.99759, 12.99932 45...
0eeae2ef-24f1-4c19-8f98-403252775992 S2A_MSIL2A_20190411T100031_N0211_R122_T32TQR_2... https://scihub.copernicus.eu/dhus/odata/v1/Pro... https://scihub.copernicus.eu/dhus/odata/v1/Pro... https://scihub.copernicus.eu/dhus/odata/v1/Pro... Date: 2019-04-11T10:00:31.024Z, Instrument: MS... 2019-04-11 10:00:31.024 2019-04-11 10:00:31.024 2019-04-11 20:22:11.623 19853 122 89.696455 68.511862 19.624598 10.278156 0.000000 0.005472 0.019103 0.000812 SAFE MSI Multi-Spectral Instrument GS2A_20190411T100031_019853_N02.11 2015-028A DESCENDING Sentinel-2A 02.11 Level-2A S2MSI2A Sentinel-2 354.73 MB S2A_MSIL2A_20190411T100031_N0211_R122_T32TQR_2... S2A_OPER_MSI_L1C_TL_MPS__20190411T121010_A0198... S2A_MSIL2A_20190411T100031_N0211_R122_T32TQR_2... 0eeae2ef-24f1-4c19-8f98-403252775992 MULTIPOLYGON (((12.93003 44.99759, 12.99932 45...
c3694ba9-2f11-40ea-9537-051bf9bb2552 S2B_MSIL2A_20190926T101029_N0213_R022_T33TUL_2... https://scihub.copernicus.eu/dhus/odata/v1/Pro... https://scihub.copernicus.eu/dhus/odata/v1/Pro... https://scihub.copernicus.eu/dhus/odata/v1/Pro... Date: 2019-09-26T10:10:29.024Z, Instrument: MS... 2019-09-26 10:10:29.024 2019-09-26 10:10:29.024 2019-09-26 21:44:42.628 13347 22 89.804619 0.478988 41.555592 0.000000 5.198079 4.666959 0.280026 0.032623 SAFE MSI Multi-Spectral Instrument GS2B_20190926T101029_013347_N02.13 2017-013A DESCENDING Sentinel-2B 02.13 Level-2A S2MSI2A Sentinel-2 806.37 MB S2B_MSIL2A_20190926T101029_N0213_R022_T33TUL_2... S2B_OPER_MSI_L1C_TL_EPAE_20190926T130133_A0133... S2B_MSIL2A_20190926T101029_N0213_R022_T33TUL_2... c3694ba9-2f11-40ea-9537-051bf9bb2552 MULTIPOLYGON (((12.46079 45.03702, 13.52703 45...
411745d3-fbb7-445d-bdba-91fa75286984 S2B_MSIL2A_20190529T101039_N0212_R022_T32TQR_2... https://scihub.copernicus.eu/dhus/odata/v1/Pro... https://scihub.copernicus.eu/dhus/odata/v1/Pro... https://scihub.copernicus.eu/dhus/odata/v1/Pro... Date: 2019-05-29T10:10:39.024Z, Instrument: MS... 2019-05-29 10:10:39.024 2019-05-29 10:10:39.024 2019-05-30 12:00:04.775 11631 22 89.872329 29.000252 60.390651 10.111997 0.000000 0.006742 0.005680 0.003202 SAFE MSI Multi-Spectral Instrument GS2B_20190529T101039_011631_N02.12 2017-013A DESCENDING Sentinel-2B 02.12 Level-2A S2MSI2A Sentinel-2 716.93 MB S2B_MSIL2A_20190529T101039_N0212_R022_T32TQR_2... S2B_OPER_MSI_L1C_TL_SGS__20190529T135452_A0116... S2B_MSIL2A_20190529T101039_N0212_R022_T32TQR_2... 411745d3-fbb7-445d-bdba-91fa75286984 MULTIPOLYGON (((12.93003 44.99759, 12.99932 45...

197 rows × 35 columns

Download prodotto ed estrazione .zip file

  • Cosa c'è dentro alla folder?
In [0]:
# Selezione e Download del prodotto
product_uuid = products_gdf_sorted['uuid'][10]
product_uuid = '50ab5827-0e38-4a40-9a68-ade23b9e601c'
product_specs = products_gdf_sorted.loc[product_uuid]
print(f"{product_specs}\nPoligono del prodotto:")
product_specs.geometry
title                          S2A_MSIL2A_20191107T100221_N0213_R122_T32TQR_2...
link                           https://scihub.copernicus.eu/dhus/odata/v1/Pro...
link_alternative               https://scihub.copernicus.eu/dhus/odata/v1/Pro...
link_icon                      https://scihub.copernicus.eu/dhus/odata/v1/Pro...
summary                        Date: 2019-11-07T10:02:21.024Z, Instrument: MS...
beginposition                                         2019-11-07 10:02:21.024000
endposition                                           2019-11-07 10:02:21.024000
ingestiondate                                         2019-11-07 17:47:34.250000
orbitnumber                                                                22856
relativeorbitnumber                                                          122
cloudcoverpercentage                                                     6.07131
highprobacloudspercentage                                                3.92452
mediumprobacloudspercentage                                               2.0982
snowicepercentage                                                       0.001084
vegetationpercentage                                                     19.0632
waterpercentage                                                          49.9948
notvegetatedpercentage                                                    12.303
unclassifiedpercentage                                                   6.87663
format                                                                      SAFE
instrumentshortname                                                          MSI
instrumentname                                         Multi-Spectral Instrument
s2datatakeid                                  GS2A_20191107T100221_022856_N02.13
platformidentifier                                                     2015-028A
orbitdirection                                                        DESCENDING
platformserialidentifier                                             Sentinel-2A
processingbaseline                                                         02.13
processinglevel                                                         Level-2A
producttype                                                              S2MSI2A
platformname                                                          Sentinel-2
size                                                                   466.76 MB
filename                       S2A_MSIL2A_20191107T100221_N0213_R122_T32TQR_2...
level1cpdiidentifier           S2A_OPER_MSI_L1C_TL_MTI__20191107T103815_A0228...
identifier                     S2A_MSIL2A_20191107T100221_N0213_R122_T32TQR_2...
uuid                                        50ab5827-0e38-4a40-9a68-ade23b9e601c
geometry                       (POLYGON ((12.93003339104157 44.99758761245698...
Name: 50ab5827-0e38-4a40-9a68-ade23b9e601c, dtype: object
Poligono del prodotto:
Out[0]:

Selezione dell'immagine a risoluzione desiderata

In [0]:
RESOLUTION = 10

if product_specs.filename in os.listdir("./data/"):
    print("Prodotto già presente")
    product_dir = F"./data/{product_specs.filename}"
else:
    print(f"Download del prodotto {product_uuid} ...")
    product_zip = api.download(product_uuid)
    zip_name = product_zip['path']
        
    print(f"Estrazione di {zip_name} ...")
    with zipfile.ZipFile(zip_name, 'r') as zip_ref:
        zip_ref.extractall("./data/")
    os.remove(zip_name)
    zip_name = f"./data/{zip_name[2:]}"
    product_dir = zip_name.replace('.zip', '.SAFE')

granule_dir = f"{product_dir}/GRANULE/"
R_dir = f"{granule_dir}{os.listdir(granule_dir)[0]}/IMG_DATA/R{RESOLUTION}m/"
print(f"Cartella delle Risoluzioni {R_dir}")
Download del prodotto 50ab5827-0e38-4a40-9a68-ade23b9e601c ...
Downloading: 100%|██████████| 489M/489M [00:23<00:00, 20.6MB/s]
MD5 checksumming: 100%|██████████| 489M/489M [00:01<00:00, 426MB/s]
Estrazione di ./S2A_MSIL2A_20191107T100221_N0213_R122_T32TQR_20191107T113907.zip ...
Cartella delle Risoluzioni ./data/S2A_MSIL2A_20191107T100221_N0213_R122_T32TQR_20191107T113907.SAFE/GRANULE/L2A_T32TQR_A022856_20191107T100402/IMG_DATA/R10m/

Caricare le immagini (formato jpg2000)

  • Che formato è?
  • Che cosa sono le bande?
In [0]:
bands_dict = get_bands_files(R_dir)
print(f"Bande trovate") 
bands_dict.keys()
Bande trovate
Out[0]:
dict_keys(['B03', 'B08', 'B04', 'B02'])
In [0]:
# Red
b4 = rio.open(bands_dict['B04'])
# Green
b3 = rio.open(bands_dict['B03'])
# Blue
b2 = rio.open(bands_dict['B02'])
# NIR
b8 = rio.open(bands_dict['B08'])

rim_info(b4)
Number of bands: 1
Dset bounds: BoundingBox(left=699960.0, bottom=4990200.0, right=809760.0, top=5100000.0)
109.8 km² high
109.8 km² wide
In [0]:
fig, axs = plt.subplots(1,4, figsize=(30,30))
show(b2, ax=axs[0], title="B02", cmap="Blues")
show(b3, ax=axs[1], title="B03", cmap="Greens")
show(b4, ax=axs[2], title="B04", cmap="Reds")
show(b8, ax=axs[3], title="B08", cmap="plasma")
plt.show()
plt.close()

Creazione immagine .tiff RGB

In [0]:
with rio.open('./imgs/RGB.tiff','w',driver='Gtiff', width=b4.width, height=b4.height, count=3,crs=b4.crs,transform=b4.transform, dtype=b4.dtypes[0]) as rgb:
    rgb.write(b2.read(1),3) # Red
    rgb.write(b3.read(1),2) # Green
    rgb.write(b4.read(1),1) # Blue

Clip dell'immagine all'area di studio

  • Le immagini native sono troppo grandi, vanno clippate alla shape selezionata
In [0]:
# Dobbiamo proiettare le coordinate sul crs dell'immagine scaricata
footprint = gpd.read_file(GEOJSON_FILE)
footprint_proj = footprint.to_crs({'init': b4.crs})

# Clip dell'immagine
with rio.open("./imgs/RGB.tiff") as src:
    out_image, out_transform = rio.mask.mask(src, footprint_proj.geometry ,crop=True)
    out_meta = src.meta.copy()
    out_meta.update({"driver": "GTiff",
                 "height": out_image.shape[1],
                 "width": out_image.shape[2],
                 "transform": out_transform})
    
with rio.open("./imgs/RGB_masked.tif", "w", **out_meta) as dest:
    dest.write(out_image)
/usr/local/lib/python3.6/dist-packages/pyproj/crs.py:77: FutureWarning: '+init=<authority>:<code>' syntax is deprecated. '<authority>:<code>' is the preferred initialization method.
  return _prepare_from_string(" ".join(pjargs))
In [0]:
#@title Contrasto { run: "auto", vertical-output: true }
CONTRAST = 4 #@param {type:"slider", min:1, max:10, step:1}

with rio.open(r"./imgs/RGB_masked.tif") as msk:

    # Correzione dati RGB
    img = msk.read([1,2,3]).astype(np.float)
    img[0] /= img[0].max()
    img[1] /= img[1].max()
    img[2] /= img[2].max()

    fig, ax = plt.subplots(1, figsize=(12, 12))
    show(np.clip((img*CONTRAST),0,1), title="Area di studio")

# Correzione canali
img_new = np.clip((img*CONTRAST*255),0,255).astype(np.uint8)
img_new = np.transpose(img_new, (1,2,0))

# Scrittura immagine
imageio.imsave("./imgs/risultato.jpg", img_new)

Sharpening Immagine

In [0]:
kernel_sharpening = np.array([[-1,-1,-1], 
                              [-1, 9,-1],
                              [-1,-1,-1]])
sharpened = cv2.filter2D(img_new, -1, kernel_sharpening)
fig, ax = plt.subplots(1, figsize=(12, 12))
ax.imshow(sharpened)
plt.title("Immagine sharpened")
imageio.imsave("./imgs/risultato_sharpened.jpg", sharpened)

Calcolo Indici

NDVI

Normalized Difference Vegetation Index

$ndvi = \frac{nir-red}{nir+red}$

In [0]:
nir = b8.read(1).astype(float)
green = b3.read(1).astype(float)
red = b4.read(1).astype(float)

ndvi = (nir-red)/(nir+red+1e-16)
In [0]:
plt.figure(figsize=(12,12))
show(ndvi+1, title="NDVI", cmap="Greens")
plt.close()

Creazione Video soglie

In [0]:
for i in range(10):
    fig = plt.figure(frameon=False)
    ax = plt.Axes(fig, [0., 0., 1., 1.])
    ax.set_axis_off()
    fig.add_axes(ax)
    ax.imshow(ndvi>(i/10), cmap="Greens")
    fig.savefig(f"./video/{i:03}.jpg", dpi=150)
    plt.close()

!ffmpeg -y -loglevel panic -framerate 1 -i "./video/00%d.jpg" ./video/query_video.mp4
playvideo("./video/query_video.mp4")
Out[0]:

Clip NDVI al footprint

In [0]:
with rio.open('./imgs/ndvi.tiff','w',driver='Gtiff', width=b4.width, height=b4.height, count=1,crs=b4.crs,transform=b4.transform, dtype='float64') as ndvi_tiff:
    ndvi_tiff.write(ndvi,1) 
In [0]:
#@title NDVI Footprint { run: "auto", vertical-output: true }

SOGLIA = 0.9 #@param {type:"slider", min:0, max:1, step:0.1}

with rio.open("./imgs/ndvi.tiff") as src:
    out_image, out_transform = rio.mask.mask(src, footprint_proj.geometry ,crop=True)
    out_image = out_image[0,:,:]
    out_image /= out_image.max()
    out_image = np.clip(out_image,0,1)
    plt.figure( figsize=(12,12))
    plt.imshow(out_image>SOGLIA, cmap='Greens')
    plt.title("NDVI footprint")
    #plt.savefig("./imgs/ndvi_masked.jpg", dpi=300)
    imageio.imwrite("./imgs/ndvi_masked.jpg", out_image)
WARNING:root:Lossy conversion from float64 to uint8. Range [0, 1]. Convert image to uint8 prior to saving to suppress this warning.
In [0]:
# Plot heatmap NDVI

plt.figure(figsize=(15,15))
plt.imshow(out_image, cmap='Greens')
plt.title("Heatmap")
Out[0]:
Text(0.5, 1.0, 'Heatmap')

Hands on LAB 👩‍💻

CVI

Ora tocca a voi...calcoliamo il Clorophyll Vegetation Index :)

  1. Implementare il calcolo dell'indice usando le bande
  2. Visualizzare l'heatmap dell'indice CVI

Calcolo Index:

$cvi = nir \cdot \frac{red}{green^{2}}$

Hints:

  • Se non visualizzi il CVI put your Data Scientist hat :)
In [0]:
#-------------------------------Calcolo CVI-------------------------------------

cvi = 

#-------------------------------------------------------------------------------

with rio.open('./imgs/cvi.tiff','w',driver='Gtiff', width=b4.width, height=b4.height, count=1,crs=b4.crs,transform=b4.transform, dtype='float64') as cvi_tiff:
    cvi_tiff.write(cvi,1) 
In [0]:
#@title CVI Footprint { run: "auto", vertical-output: true }

SOGLIA = 2.5 #@param {type:"slider", min:0, max:10, step:0.5}


with rio.open("./imgs/cvi.tiff") as src:
  out_image, out_transform = rio.mask.mask(src, footprint_proj.geometry ,crop=True)
  out_image = out_image[0,:,:]

  plt.figure(figsize=(12,12))
  plt.imshow(out_image>SOGLIA, cmap="Greens")
  plt.title("CVI Footprint")
In [0]:
#-------------Riusciamo a visualizzare lheatmap delll'indice?-------------------



#-------------------------------------------------------------------------------
In [0]:
# Rilascio Risorse 
# Attenzione se esegue questa cella, non è più possibile usare le bande
b2.close()
b3.close()
b4.close()
b8.close()

Now it's up to you :)

Thanks :)

Risorse