mercoledì 31 maggio 2023

Batimetria Venezia da Landsat con Decision Tree

Visto lo scarso successo del metodo analitico visto qui ho provato un metodo alternativo citato spesso il letteratura che prevede la determinazione della batimetria da dati ottici usando la rete neurale decision tree

Per cambiare ho preso i dati Landsat sulla Laguna di Venezia (la batimetria della Laguna di Venezia e' molto piu' variabile rispetto a quella della lagunan di Orbetello) usando come dato di verita' a terra questo geotiff http://cigno.ve.ismar.cnr.it/layers/geonode%3Alag02_okart_export

Su Qgis sono stati importate le bande RGB,NIR di Landsat8  e dopo avere creato una griglia regolare di punti spaziati di 30 m e' stata estratta una tabella con i valori di riflettanza per ogni banda e la profondita'


 Una volta ottenuto il file CSV i dati di batimetria sono stati divisi in classi di 0.5 m per rendere possibile la successiva elaborazione tramite un semplice script in GO 


package main

import (
    "encoding/csv"
    "fmt"
    "io"
    "log"
    "math"
    "os"
    "strconv"
)

func main() {
    f, err := os.Open("tree.csv")
    if err != nil {
        log.Fatal(err)
    }
    defer f.Close()
    csvReader := csv.NewReader(f)
    for {
        rec, err := csvReader.Read()
        if err == io.EOF {
            break
        }
        if err != nil {
            log.Fatal(err)
        }
        tt, _ := strconv.ParseFloat(rec[0], 'f')
        t := math.Abs(tt)
        fmt.Print(math.Floor(t / 0.5))
        fmt.Println("," + rec[1] + "," + rec[2] + "," + rec[3] + "," + rec[4])

    }
}


il file finale ha un formato del tipo

 classe,red,nir,gren,blue
3,6707,5566,8241,9397
3,6714,5575,8221,9375
3,6696,5573,8184,9369
3,6665,5577,8144,9331
3,6638,5584,8089,9287
3,6636,5568,8080,9281

In totale sono emerse 23 classi per un totale di 16000 punti


Come si vede la distribuzione e' fondamentalmentre asimmetrica con le prima 4 classi che rappresentano la gran parte dei dati. E' stato effettuato un taglio dei dati alle prime 6 classi

 Il file CSV e' stato utilizzato  all'interno dello script seguente in Colab (si tratta di un semplice adattamento del programma di esempio di Tensorflow per la rete  decision tree)


# -*- coding: utf-8 -*-
"""decisiontree_venezia.ipynb

Automatically generated by Colaboratory.

Original file is located at
    https://colab.research.google.com/drive/1f8HLdjHiDNvpplKzNOVrXuIXrKTay0TS
"""

!pip install tensorflow_decision_forests

import numpy as np
import pandas as pd
import tensorflow_decision_forests as tfdf

path = "/content/classi_nobat_ridotto.csv"
pandas_dataset = pd.read_csv(path)

# Display the first 3 examples.
pandas_dataset.head(3)

np.random.seed(1)
# Use the ~10% of the examples as the testing set
# and the remaining ~90% of the examples as the training set.
test_indices = np.random.rand(len(pandas_dataset)) < 0.1
pandas_train_dataset = pandas_dataset[~test_indices]
pandas_test_dataset = pandas_dataset[test_indices]

print("Training examples: ", len(pandas_train_dataset))

print("Testing examples: ", len(pandas_test_dataset))

tf_train_dataset = tfdf.keras.pd_dataframe_to_tf_dataset(pandas_train_dataset, label='classe')
model = tfdf.keras.CartModel()
model.fit(tf_train_dataset)

tfdf.model_plotter.plot_model_in_colab(model, max_depth=10)

model.compile("accuracy")
print("Train evaluation: ", model.evaluate(tf_train_dataset, return_dict=True))

tf_test_dataset = tfdf.keras.pd_dataframe_to_tf_dataset(pandas_test_dataset, label='classe')
print("Test evaluation: ", model.evaluate(tf_test_dataset, return_dict=True))

from keras.utils import plot_model

plot_model(
    model,
    to_file='model.png',
    show_shapes=False,
    show_dtype=False,
    show_layer_names=True,
    rankdir='TB',
    expand_nested=False,
    dpi=96,
    layer_range=None,
    show_layer_activations=False,
    show_trainable=False
)

!pip install keras-tuner

import keras_tuner as kt

def build_model(hp):
  model = tfdf.keras.CartModel(
      min_examples=hp.Choice("min_examples",
          # Try four possible values for "min_examples" hyperparameter.
          # min_examples=10 would limit the growth of the decision tree,
          # while min_examples=1 would lead to deeper decision trees.
         [1, 2, 5, 10]),
      validation_ratio=hp.Choice("validation_ratio",
         # Three possible values for the "validation_ratio" hyperparameter.
         [0.0, 0.05, 0.10]),
      )
  model.compile("accuracy")
  return model

tuner = kt.RandomSearch(
    build_model,
    objective="val_accuracy",
    max_trials=10,
    directory="/tmp/tuner",
    project_name="tune_cart")

tuner.search(x=tf_train_dataset, validation_data=tf_test_dataset)
best_model = tuner.get_best_models()[0]

print("Best hyperparameters: ", tuner.get_best_hyperparameters()[0].values)
# >> Best hyperparameters:  {'min_examples': 2, 'validation_ratio': 0.0}

model = tfdf.keras.CartModel(min_examples=2, validation_ratio=0.0)
model.fit(tf_train_dataset)

model.compile("accuracy")
print("Test evaluation: ", model.evaluate(tf_test_dataset, return_dict=True))

tfdf.model_plotter.plot_model_in_colab(model, max_depth=10)

Training examples: 13463 Testing examples: 1520


 



al termine si ha una accuracy della porzione di dati di test superiore al 70%

14/14 [==============================] - 1s 8ms/step - loss: 0.0000e+00 - accuracy: 0.7761 Train evaluation: {'loss': 0.0, 'accuracy': 0.7761271595954895} 2/2 [==============================] - 0s 19ms/step - loss: 0.0000e+00 - accuracy: 0.7388 Test evaluation: {'loss': 0.0, 'accuracy': 0.7388157844543457} 



In generale la rete nel caso di incertezza di una classe seleziona sempre classi contingue

 

giovedì 25 maggio 2023

Batimetria Sentinel 2 Laguna Orbetello

 Aggiornamento : forse ho trovato il motivo per il quale non riuscivo a trovare correlazione tra batimetria e dato di controllo. Il grafico sottostante e' stato ricavato da dati Landsat ma con la stessa metodologia che ho provato. Come si vede la correlazione si sviluppa a partire da 4 m di profondita, per profondita' inferiore l'indice non e' utilizzabile. La laguna di Orbetello ha profondita' praticamente ovunque inferiore ai 3 m

 


 

BathymetryMapping Using Landsat 8 Satellite Imagery Jagalingam P1, Akshaya B J1and Arkal Vittal Hegde doi: 10.1016/j.proeng.2015.08.326

 

Ho provato a seguire il tutorial https://www.youtube.com/watch?v=xD1VQbnfasw&t=1923s per vedere se riuscito a trovare una correlazione tra il dato telerilevato ed i dati Lidar di batimetria della Laguna di Orbetello 

Sono partito dall'immagine S2A_MSIL2A_20220806T100611_N0400_R022_T32TPN_20220806T162658, ho effettuato un subset solo sull'area di interesse ed effettuato un resampling per avere le bande 2,3,4 ed 8 tutte a 10 m (Raster/Geometric/Resampling)


 

A questo punto si maschera il terreno mediante Raster/Band Math e creando una formula con

if  B8 > 0.05 then NaN else 1


una volta ottenuta questa maschera si moltiplicano le bande B2,B3,B4,B8 (sempre tramite band math) per il valore della maschera


A questo punto si deve passare alla Sun Glint Correction (questa e' la procedura manuale..nel Plugin Sen2Coral di SNAP in Optical/Thematic Water Processing/Sen2Coral/Processing Modules/Deglint Processor)

Si creano dei poligoni (o si importa uno shapefile poligonale) 

Si crea quindi uno scatterplot (mediante il tool di Snap) indicando come ROI Mask il file poligonale, come X la banda 8 e come Y le altre bande (2,3,4) e si clicca Refresh


si osserva una correlazione (molto blanda nel mio caso, nel tutorial e' molto migliore). Si clicca sul grafico  e si seleziona Copy Data to Clipboard)

Ho provato con una immagine della Laguna di Venezia ma per assurdo i risultati sono stati ancora peggiori


 

 

Si copiano i dati in Libreoffice od in R e si calcola retta di correlazione tra le bande

si ripete la correlazione tra B8 e B2,B3, B4 estraendo le rette di calibrazione

Si riapre il Raster Calculator e si rimuove il Sun Glint tramite

B2_Land_Mask -  m*( B8_Land_Mask-c)

m = coefficiente angolare della retta di correlazione

c = valore minimo  della banda B2_Land_Mask che si puo' ricavare dall'istogramma


 
 Le nuove bande saranno B2_Deglint, B3_Deglint e B4_Deglint. Il composit sara' l'immagine sottostante


Ultimo passo. Dal Deglint si passa alla DOS sottraendo al Deglint il valore minimo sempre mediante l'istogramma

B3DOS = B3_Deglint - 0.003

Alla fine si puo' applicare la correlazione di Stumph Linear Ratio Model (Stumpf, R. P., Holderied, K., & Sinclair, M. (2003). Determination of water depth with high-resolution satellite imagery over variable bottom
types. Limnology and Oceanography, 48(1), 547–556.)

log(1000 *B3DOS)/log(1000 *B2DOS)

si esporta la mappa in Geotiff e la si importa in QGis insieme allo shape della batimetria misurata tramite Lidar per verificare una eventuale correlazione tra dato telerilevato ottico  e dato reale

 


Tramite il plugin Point Sampling Tool si crea una tabella in cui sono riportate alla stessa posizione geografica il valore dell'indice telerilevato ed il dato di batimetria Lidar (i due layers devono essere nello stesso sistema di riferimento). Effettuando uno scatterplot (sono circa 230.000 punti e sono stati elaborati in R per difficolta' di Calc)

rm()
dati <- read.csv("C:\\Users\\l.innocenti\\Desktop\\orbetello_L2A\\confronto\\confronto.csv")


y <- dati$field_3
x <- dati$step1_resampled_extractor

plot(x,y, main="Batimetria vs Sentinel", xlab="Indice SDB", ylab="Batimetria (m)",pch=20, xlim=c(0.85,1.0))

model <- lm(y ~ x, data = dati)
summary(model)


il grafico del risultato finale ... sostanzialmente un insuccesso





mercoledì 17 maggio 2023

Plugin OrfeoToolbox in QGis Windows

 Per utilizzare Orfeo Toolbox direttamente dentro QGis si spacchetta lo zip di Orfeo Toolbox in una directory (con il nome senza caratteri speciali). Poi si seleziona strumenti di processing/Options /Programmi e si popolano i campi 

Cartella OTBC:/OTB-7.4.1-Win64/OTB-7.4.1-Win64

Cartella delle applicazioni OTB C:/OTB-7.4.1-Win64/OTB-7.4.1-Win64/bin;C:/OTB-7.4.1-Win64/OTB-7.4.1-Win64/lib/otb/applications




Pansharpening Landsat con Qgis

 

Si caricano i raster delle bande 2,3,4 (BGR) e la banda 8 pancromatica

Si apre il menu Raster/Miscellanea/Crea Raster Virtuale selezionando come input le bande ottiche e spuntando il flag Place each input file in a separate band

A questo punto dal toolbox si seleziona pansharpening indicando come Dataset spettrale il raster virtuale creato in precedenza e come dataset pancromatico la banda 8




Snap e Landsat

 Per utilizzare le immagini Landsat all'interno del software Snap di Esa e' sufficiente spacchettare il file tar ed aprire il file txt con contiene nel nome la sigla MTL




sabato 6 maggio 2023

RGB change detection con RasterIO

 Un semplice metodo per avere il change detection da una immagine RGB e' la formula seguente

 


 che non e' altro che la distanza euclidea tra i due punti rappresentativi delle immagini nello spazio delle bande (quindi puo' essere scalato anche su n-bande)

 


 utilizzando la libreria RasterIO si puo' scrivere il codice seguente

 

import numpy as np
import rasterio
from rasterio.plot import show_hist,show

immagine1 = "cd1.tif"
immagine2 = "cd2.tif"


im1 = rasterio.open(immagine1)
im2 = rasterio.open(immagine2)

im1_b2 = im1.read(1).astype(float)
im1_b3 = im1.read(2).astype(float)
im1_b4 = im1.read(3).astype(float)


im2_b2 = im2.read(1).astype(float)
im2_b3 = im2.read(2).astype(float)
im2_b4 = im2.read(3).astype(float)

np.seterr(divide = "ignore", invalid = "ignore")

change_det = np.zeros(im1_b2.shape, dtype=rasterio.float32)


change_det = np.sqrt(np.power(im1_b2-im2_b2,2)+np.power(im1_b3-im2_b3,2)+np.power(im1_b4-im2_b4,2))


#print("Change Det Max " + str(np.max(change_det)))
#print("Change Det Min " + str(np.min(change_det)))

#show(change_det)

#show_hist(change_det)
kwargs = im1.meta
kwargs.update(driver='GTiff',dtype=rasterio.float32,count=1)

cd_image = rasterio.open('CD.tiff','w',**kwargs )
cd_image.write(change_det,1)
cd_image.close()

 

mercoledì 26 aprile 2023

L'importanza di una antenna UV-5118

 Mi sono comprato su Aliexpress questa radio UV-5118 che era venduta con Air Band...una volta arrivata non solo non riceveva in air band ma praticamente a nessuna frequenza 



Dopo aver ottenuto il rimborso ho sostituito l'antenna con una presa da un Baofeng...la air band non si riceve ma in UHF e VHF ha iniziato a funzionare in modo decisamente buono


lunedì 24 aprile 2023

Satellite Derived Bathymetry Mapping

Una applicazione dello script a questo link applicato direttamente in EO Browser


Versilia

Booca L'Arno

Lago di massaciuccoli

Golfo di Spezia Foce del Magra

in linea di principio lo script puo' essere semplificato dato che la batimetria puo' essere stimata in modo relativa con il rapporto log10(B3/B4) di Sentinel 2 (attenzione...Rrs non e' la Surface Reflectance che si trova nel prodotto L2 di Sentinel ma e' la Remote Sensing Reflectance)




in ogni caso si vede che l'influenza del carico di sedimenti influenza la stima della batimetria (vedi immagine successiva. Batimetria ripresa da progetto Camp Italy)





venerdì 21 aprile 2023

Correlazione tra Sentinel 5P e stazioni a terra

In questo post, usando gli script di Earth Engine visti in precedenza, ho provato a mettere in correlazione il dato telerilevato di Sentinel 5P con alcune stazioni di monitoraggio aria Arpat (dati pubblici sul sito www.arpat.toscana.it) 


Parametro NO2
Vi e' un ottimo accordo tra i dati medi mensili delle concentrazioni di NO2 da satellite ed a terra

la curva di correlazione non e' incoraggiante



Parametro CH4
I dati da satellite mostrano il gradiente di aumento del metano che e' coerente con il valore di crescita mondiale 




Parametro S02
Per il parametro S02 non si possono fare correlazioni perche' quando le concentrazioni aumentano (nel periodo autunnale) il satellite non riporta dati
Stazione Arpat via Bassi Firenze

Mappa SO2 Piana Fiorentina Giugno 2019
Sentinel 5P





Parametro O3
Per l'ozono ho molti dubbi...comunque esporti i dati si vede che c'e' un offset di circa 3 mesi tra i dati telerilevati e quelli a terra

Parametro CO
Questo parametro e' critico per la correlazione con la stazione a terra perche' a Firenze la stazione di misura non e' in posizione di fondo ma a bordo di una viabilita' principale



Parametro PM10
Il parametro PM10 puo' essere derivato da UV Aerosol nel dataset di Sentinel 5P
Qui il  confronto delle medie mensili tra i dati satellitari ed una stazione a terra




Aerosol marzo 2022



qui un riferimento bibliografico della correlazione tra dati satellitari ed a terra


Earth Engine Sentinel 5P time series NO2

 Due script per scaricare serie tempo dei dati Sentinel 5P da Google Earth Engine dato un punto geografico




Tutti i dati

================================================================

var point = ee.Geometry.Point([11.097849, 43.793234]);
var sentinel = ee.ImageCollection('COPERNICUS/S5P/NRTI/L3_NO2');
var sentinelLST = sentinel.filterBounds(point)
.filterDate('2019-01-01', '2022-12-31')
.select('NO2_column_number_density');

sentinelLST = sentinelLST.map(function(img){
var date = img.get('system:time_start');
return img.multiply(100000).set('system_time_start', date);
});

var createTS = function(img){
var date = img.get('system_time_start');
var value = img.reduceRegion(ee.Reducer.mean(), point).get('NO2_column_number_density');
var ft = ee.Feature(null, {'system:time_start': date,
'date': ee.Date(date).format('Y/M/d'),
'value': value});
return ft;
};

var TS = sentinelLST.map(createTS);

var graph = ui.Chart.feature.byFeature(TS, 'system:time_start', 'value');

print(graph.setChartType("Sentinel 5P")
.setOptions({vAxis: {title: 'NO2'},
hAxis: {title: 'Date'}}));


Export.table.toDrive({collection: TS, selectors: 'date, value'});

================================================================

Medie Giornaliere

================================================================

var collection = ee.ImageCollection('COPERNICUS/S5P/OFFL/L3_NO2')
  .select('tropospheric_NO2_column_number_density')
var daily_data = ee.ImageCollection(ee.List.sequence(2019,2019).map(function(year){
  var date1 = ee.Date.fromYMD(year,1,1)
  var date2 = date1.advance(1,'year')
  //Calculate the number of days per year
  var doy = date2.difference(date1,'day')
  var doyList = ee.List.sequence(1,doy)
  //Daily image mean synthesis using doy
  var day_imgs = doyList.map(function(doy){
    doy = ee.Number(doy)
    var temp_date = date1.advance(doy.subtract(1),"day")
    var temp_img = collection.filterDate(temp_date,temp_date.advance(1,'day'))
    return temp_img.mean().set("system:time_start",temp_date.millis())
  })
  return day_imgs
}).flatten())
Map.addLayer(daily_data)
var chart = ui.Chart.image.series({
imageCollection:daily_data,
region:roi,
reducer:ee.Reducer.mean(),
scale:1113.2,
// xProperty:,
})
print(chart)

================================================================


Medie Mensili

================================================================


var point = ee.Geometry.Point([11.097849, 43.793234]);
var dataset = ee.ImageCollection('COPERNICUS/S5P/NRTI/L3_NO2')
          .filterBounds(point)
          .filterDate('2019-01-01', '2022-12-31')
          .select('NO2_column_number_density');
var months = ee.List.sequence(1, 12);
var start_year = 2019;
var start_date = '2019-01-01';
var end_year = 2022;
var end_date = '2022-12-31';
var years = ee.List.sequence( start_year, end_year);

var byMonthYear =  ee.FeatureCollection(
  years.map(function (y) {
    return months.map(function(m){
          var w = dataset.filter(ee.Filter.calendarRange(y, y, 'year'))
                    .filter(ee.Filter.calendarRange(m, m, 'month'))
                    .mean();
      var pointMean = w.reduceRegion({reducer:ee.Reducer.first(), geometry:point,scale:1000});  
      return ee.Feature(null).set("value",pointMean.get("NO2_column_number_density")).set("year",y).set("month",m);
    })
  }).flatten()
);

print("feature collection",byMonthYear);

Export.table.toDrive({collection:byMonthYear,description:"O3_signa"})

================================================================


martedì 18 aprile 2023

Cuda Mandelbrot

 


 

 

#include <stdio.h>
//questa libreria crea png senza altre dipendenze
#include "libattopng.h"

#define W  5000 //width image
#define H  5000 //height image
#define TX 32 // number of threads per block along x-axis
#define TY 32 // number of threads per block along y-axis

__global__
void Kernel(int *d_out, int w, int h, int itera)
{
    //nel kernel viene lanciato un thread per ogni pixel dell'immagine
    //per calcolare la riga e la colonna si usa la formula sottostante
    int c = blockIdx.x*blockDim.x + threadIdx.x; // colonna
    int r = blockIdx.y*blockDim.y + threadIdx.y; // riga
    int i = r*w + c; // posizione del pixel nell'array lineare
    if ((c >= w) || (r >= h)) return;

    // inizia il ca
    const float x_min = -0.727;
    const float x_max = -0.730;
    const float y_min = -0.247;
    const float y_max = -0.250;

    float dx,dy;

    dx = (x_max-x_min)/w;
    dy = (y_max-y_min)/h;


    float k = x_min+(r*dx);
    float j = y_min+(c*dy);
    d_out[i] = 0;

    double x,y,x_new, y_new = 0.0;
    for (int f=0; f<itera;f++)
            {
                    x_new = (x*x) - (y*y) + k;
                    y_new = (2*x*y) +j;
                    if (((x_new*x_new)+(y_new*y_new))>4)
                    {
                        d_out[i] = f;
                        return;
                    }
                    x = x_new;
                    y = y_new;
            }
 }

int main()
{
 // alloca e mette a zero (malloc alloca e basta) un array di int di dimensione u
 // uguale all'immagine desiderata sulla ram della CPU
 int *out = (int*)calloc(W*H, sizeof(int));

 // alloca un array nella GPU delle stesse dimensioni
 // la GPU viene definita come device mentre la CPU e' l'host
 int *d_out; // pointer for device array
 cudaMalloc(&d_out, W*H*sizeof(int));

 // definisce i threads
 const dim3 blockSize(TX, TY);
 const int bx = (W + TX - 1)/TX;
 const int by = (W + TY - 1)/TY;
 const dim3 gridSize = dim3(bx, by);
 //lancia il kernel sulla GPU passando come parametro l'array sulla GPU
 Kernel<<<gridSize, blockSize>>>(d_out, W, H,4096);

 //trasferisce il contenuto dell'array dalla GPU alla RAM della CPU
 cudaMemcpy(out, d_out, W*H*sizeof(int), cudaMemcpyDeviceToHost);

 //crea una PNG partendo dall'array
 libattopng_t* png = libattopng_new(W, H, PNG_GRAYSCALE);
 
 int x, y;
 for (y = 0; y < W; y++) {
   for (x = 0; x < H; x++) {
     libattopng_set_pixel(png, y, x, out[x+y*H]%255);
   }
 }
 libattopng_save(png, "mandelbrot.png");
 libattopng_destroy(png);


 cudaFree(d_out);
 free(out);
 return 0;
 }
 

 

mercoledì 12 aprile 2023

Trasformazione affine con OpenCV

 Alla fine degli anni 80 se non erro alla Domenica Sportiva c'era un  primordiale sistema di computer grafica che consentiva di valutare il fuorigioco in una partita di calcio da immagini televisive....mi ero sempre chiesto come era possivile....dopo tanto tempo ho visto che e' possibile tramite la trasformazione affine 

 Partiamo da una immagine generica dello stadio di Firenze


 Le dimensione del campo dell'Artemio Franchi sono 105x68 m mentre nell'immagine sottostante sono riportate le dimensioni ufficiali del campo da calcio


 La trasformazione affine serve a trasformare le coordinate immagine in un altro sistema di coordinate (in questo caso coordinate centrimetriche reali con origine degli assi nella bandierina del calcio d'angolo in basso a sinistra)
OpenCV richiede tre punti ...per esempio la bandierina del calcio d'angolo in alto a sinistra ha coordinate pixel di 362x208 mentre coordinate reali 10500,6800 cm. I punti sono stati selezionati sull'incrocio di alcune linee dell'area di rigore
Nell'immagine successiva il risultato dell'algoritmo...non perfetto perche' le linee dovrebbero essere ortogonali ma considerando le incertezze direi che non e'male

 
import matplotlib.pyplot as plt
import numpy as np
import cv2 as cv

img = cv.imread('fiorentina.jpg')
pts1 = np.float32([[193,132],[362,208],[542,214]])
pts2 = np.float32([[10500,6800],[9950,4315],[10500,3765]])

M = cv.getAffineTransform(pts1,pts2)
dst = cv.warpAffine(img,M,(10500,6800))

plt.imshow(dst)
plt.show()
plt.savefig("dest.png")
plt.close(dst)


 

LLama3 Anita

A seguito di questo post ho provato a vedere ho provato a vedere cosa accadeva ad utilizzare un modello specifico per la lingua italiana in...