Visualizzazione post con etichetta Telerilevamento. Mostra tutti i post
Visualizzazione post con etichetta Telerilevamento. Mostra tutti i post

venerdì 3 maggio 2024

Regressione con tensorflow su dati di carbonati in suoli naturali

 Dopo aver trovato questo esempio sulla regressione con Tensorflow/Keras ho ritirato fuori un set di misure iperspettrali di laboratorio effettuato con FieldSpec in laboratorio per vedere se il metodo poteva essere utilizzato per la regressione della concentrazione di carbonati in campioni di terreno naturali

I campioni analizzati erano stati tutti setacciati a 0.075 mm ed erano stati passati in forno per eliminare l'umidita'. Di seguito gli spettri

Tutti i campioni sono stati analizzati in laboratorio per la determinazione chimica del contenuto in carbonato di calcio

Gli spettri sono stati letti dai file originali in .asd e convertiti in un csv mediante la libreria Python SpecDal 

L'ultima colonna e' il dato di laboratorio mentre le altre colonne corrispondono alla riflettanza alle differenti lunghezze d'onda e costituiscono le features


Ho provato a non effettuare la rimozione del continuum perche' le misure sono fatte in condizioni controllate su materiale preparato (con parita' di granulometria ed umidita')

Mediante lo script sottostante e' stata effettuata la regressione fino a 100 epochs (non si avevano vantaggi per numeri superiori). Il dataset originario di 40 misure e' stato diviso an 80% di addestramento (di cui 20% di validazione) e 20% di test



Dataset di validazione

Dataset di test

Durante il dottorato, su una serie differente ed usando la profondita' di picco normalizzata a 2345 nm, avevo avuto una regressione migliore utilizzando anche una matematica piu' semplice


L'aspetto interessante di questo approccio e' che non e' necessario conoscere a priori la lunghezza d'onda di assorbimento desiderato. L'algoritmo pur non conoscendolo esplicitamente lo invidua nelle features in modo autonomo


-*- coding: utf-8 -*-


import matplotlib.pyplot as plt
import numpy as np
import pandas as pd

np.set_printoptions(precision=3, suppress=True)
import tensorflow as tf

from tensorflow import keras
from tensorflow.keras import layers

from google.colab import drive
drive.mount('/content/drive')

raw_dataset = pd.read_csv("/content/drive/MyDrive/calcite_vol_75.csv", sep=',')
dataset = raw_dataset.copy()

train_dataset = dataset.sample(frac=0.8, random_state=1)
test_dataset = dataset.drop(train_dataset.index)

print(train_dataset)

train_features = train_dataset.copy()
test_features = test_dataset.copy()

train_labels = train_features.pop('Calcite')
test_labels = test_features.pop('Calcite')

normalizer = tf.keras.layers.Normalization(axis=-1)
normalizer.adapt(np.array(train_features))

def build_and_compile_model(norm):
  model = keras.Sequential([
      norm,
      layers.Dense(64, activation='relu'),
      layers.Dense(64, activation='relu'),
      layers.Dense(1)
  ])

  model.compile(loss='mean_absolute_error',
                optimizer=tf.keras.optimizers.Adam(0.001))
  return model

def plot_loss(history):
  plt.plot(history.history['loss'], label='loss')
  plt.plot(history.history['val_loss'], label='val_loss')
  plt.ylim([0, 10])
  plt.xlabel('Epoch')
  plt.ylabel('Error')
  plt.legend()
  plt.grid(True)

dnn_model = build_and_compile_model(normalizer)
dnn_model.summary()

plot_loss(history)

test_predictions = dnn_model.predict(test_features).flatten()

a = plt.axes(aspect='equal')
plt.scatter(test_labels, test_predictions)
plt.xlabel('True Values')
plt.ylabel('Predictions')
lims = [0, 30]
plt.xlim(lims)
plt.ylim(lims)
_ = plt.plot(lims, lims)

error = test_predictions - test_labels
plt.hist(error, bins=25)
plt.xlabel('Prediction Error ')
_ = plt.ylabel('Count')




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

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()

 

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


Tour de France Firenze Gran Depart

  Poagcar Wout Van Aert Vingegaard       Van Der Poel