venerdì 20 marzo 2026

Spettri di Prisma

Sono ritornato a lavorare sui dati Prisma, stavolta in maniera piu' convinta

Il primo problema e' trovare il software giusto. Se si aprono le immagini con Esa Snap non ci sono particolari problemi tranne quando si arriva ad aprire Spectrum View e si scopre che le bande dell'immagini sono divise in due VNIR e SWIR e quindi non e' possibile avere uno spettro completo da 400 a 2500 nm. Si devono aprire le due immagini ed a seconda di dove e' il cursore Spectrum View si spostera' tra i dati VNIR e SWIR 

 


Per avere uno spettro completo l'unica soluzione e' stata passare da uno script Python (compilato via Claude AI)

 


"""
PRISMA Hyperspectral Spectrum Plotter (L2C)
=============================================
Reads a PRISMA L2C HE5 file and plots the full VNIR+SWIR spectrum
for a user-selected pixel.

Confirmed HDF5 layout:
Cubes : HDFEOS/SWATHS/PRS_L2C_HCO/Data Fields/{VNIR,SWIR}_Cube
shape = (rows, bands, cols)
Wavelengths : root attrs List_Cw_Vnir / List_Cw_Swir (nm, length=n_bands)
Band flags : root attrs List_Cw_Vnir_Flags / List_Cw_Swir_Flags (1=valid)
Scale : root attrs L2ScaleVnirMin/Max, L2ScaleSwirMin/Max
DN → reflectance = DN / 65535 * (ScaleMax - ScaleMin) + ScaleMin

Usage
-----
python prisma_spectrum_plot.py <file.he5> [row] [col] [-o out.png]

Dependencies
------------
pip install h5py numpy matplotlib
"""

import argparse
import numpy as np
import h5py
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
from pathlib import Path


CUBE_VNIR = "HDFEOS/SWATHS/PRS_L2C_HCO/Data Fields/VNIR_Cube"
CUBE_SWIR = "HDFEOS/SWATHS/PRS_L2C_HCO/Data Fields/SWIR_Cube"


def load_prisma(filepath: str, row: int | None = None, col: int | None = None):
"""
Extract a single-pixel VNIR+SWIR spectrum from a PRISMA L2C HE5 file.

Parameters
----------
filepath : path to the .he5 file
row, col : pixel coordinates (0-based). Default = image centre.

Returns
-------
wavelengths : np.ndarray – wavelengths in nm (valid bands only, sorted)
spectrum : np.ndarray – reflectance [0–1]
meta : dict
"""
filepath = Path(filepath)
if not filepath.exists():
raise FileNotFoundError(filepath)

with h5py.File(filepath, "r") as f:

# ── wavelengths and valid-band flags from root attributes ─────────────
vnir_wl = np.array(f.attrs["List_Cw_Vnir"], dtype=np.float32)
swir_wl = np.array(f.attrs["List_Cw_Swir"], dtype=np.float32)
vnir_flags = np.array(f.attrs["List_Cw_Vnir_Flags"], dtype=np.int8)
swir_flags = np.array(f.attrs["List_Cw_Swir_Flags"], dtype=np.int8)

vnir_valid = vnir_flags == 1 # boolean mask for valid VNIR bands
swir_valid = swir_flags == 1 # boolean mask for valid SWIR bands

# ── scale factors: uint16 DN → reflectance ────────────────────────────
vnir_scale_min = float(f.attrs["L2ScaleVnirMin"])
vnir_scale_max = float(f.attrs["L2ScaleVnirMax"])
swir_scale_min = float(f.attrs["L2ScaleSwirMin"])
swir_scale_max = float(f.attrs["L2ScaleSwirMax"])

# ── cube shapes: (rows, bands, cols) ──────────────────────────────────
vnir_ds = f[CUBE_VNIR]
swir_ds = f[CUBE_SWIR]
n_rows, n_vnir_bands, n_cols = vnir_ds.shape
_, n_swir_bands, _ = swir_ds.shape

if row is None: row = n_rows // 2
if col is None: col = n_cols // 2

if not (0 <= row < n_rows and 0 <= col < n_cols):
raise ValueError(
f"Pixel ({row}, {col}) outside image bounds ({n_rows}x{n_cols})."
)

# ── read single pixel: cube[row, :, col] ─────────────────────────────
vnir_dn = vnir_ds[row, :, col].astype(np.float32)
swir_dn = swir_ds[row, :, col].astype(np.float32)

# ── DN → reflectance ──────────────────────────────────────────────────────
vnir_ref = vnir_dn / 65535.0 * (vnir_scale_max - vnir_scale_min) + vnir_scale_min
swir_ref = swir_dn / 65535.0 * (swir_scale_max - swir_scale_min) + swir_scale_min

# ── apply valid-band masks ────────────────────────────────────────────────
# Flags array length matches n_bands; trim to cube size just in case
vnir_mask = vnir_valid[:n_vnir_bands]
swir_mask = swir_valid[:n_swir_bands]

vnir_wl = vnir_wl[:n_vnir_bands][vnir_mask]
vnir_ref = vnir_ref[vnir_mask]
swir_wl = swir_wl[:n_swir_bands][swir_mask]
swir_ref = swir_ref[swir_mask]

# ── sort by wavelength (PRISMA stores bands longest-first) ───────────────
def sort_wl(wl, sp):
idx = np.argsort(wl)
return wl[idx], sp[idx]

vnir_wl, vnir_ref = sort_wl(vnir_wl, vnir_ref)
swir_wl, swir_ref = sort_wl(swir_wl, swir_ref)

# ── remove VNIR/SWIR overlap: keep VNIR below SWIR start ─────────────────
vnir_keep = vnir_wl < swir_wl[0]
wavelengths = np.concatenate([vnir_wl[vnir_keep], swir_wl])
spectrum = np.concatenate([vnir_ref[vnir_keep], swir_ref])

meta = dict(
row=row, col=col,
n_rows=n_rows, n_cols=n_cols,
n_vnir_valid=int(vnir_mask.sum()),
n_swir_valid=int(swir_mask.sum()),
filename=filepath.name,
)
return wavelengths, spectrum, meta


# ── plot ───────────────────────────────────────────────────────────────────────

COLOUR_REGIONS = [
(400, 700, "#e8f4e8", "VIS"),
(700, 1000, "#f5e8f5", "NIR"),
(1000, 1800, "#e8eef5", "SWIR-1"),
(1800, 2500, "#f5f0e8", "SWIR-2"),
]

ABSORPTION_BANDS = [
(1340, 1460, "H\u2082O"),
(1800, 1960, "H\u2082O"),
]


def plot_spectrum(wavelengths, spectrum, meta, output_path=None):
fig, ax = plt.subplots(figsize=(13, 5))

wl_min, wl_max = wavelengths[0], wavelengths[-1]
sp_max = np.nanmax(spectrum)

# Spectral region backgrounds
for lo, hi, colour, label in COLOUR_REGIONS:
lo_c, hi_c = max(lo, wl_min), min(hi, wl_max)
if lo_c < hi_c:
ax.axvspan(lo_c, hi_c, color=colour, alpha=0.45, zorder=0)
ax.text((lo_c + hi_c) / 2, sp_max * 1.02,
label, ha="center", va="bottom", fontsize=8,
color="#888888", zorder=2)

# Atmospheric absorption windows
for lo, hi, label in ABSORPTION_BANDS:
lo_c, hi_c = max(lo, wl_min), min(hi, wl_max)
if lo_c < hi_c:
ax.axvspan(lo_c, hi_c, color="#bbbbbb", alpha=0.55, zorder=1)
ax.text((lo_c + hi_c) / 2, sp_max * 0.96,
label, ha="center", va="top", fontsize=8,
color="#444444", zorder=3)

ax.plot(wavelengths, spectrum, color="#1a5276", linewidth=1.3, zorder=4)
ax.fill_between(wavelengths, spectrum, alpha=0.12, color="#1a5276", zorder=3)

ax.set_xlim(wl_min, wl_max)
ax.set_ylim(bottom=0)
ax.set_xlabel("Wavelength (nm)", fontsize=11)
ax.set_ylabel("Reflectance", fontsize=11)
ax.set_title(
f"PRISMA L2C Spectrum - pixel ({meta['row']}, {meta['col']}) "
f"[{meta['n_rows']}x{meta['n_cols']}]\n{meta['filename']}",
fontsize=10, pad=8
)
ax.xaxis.set_minor_locator(ticker.AutoMinorLocator(5))
ax.yaxis.set_minor_locator(ticker.AutoMinorLocator(4))
ax.tick_params(which="both", direction="in")
ax.grid(True, which="major", linestyle="--", alpha=0.35)
ax.annotate(
f"VNIR: {meta['n_vnir_valid']} bands | SWIR: {meta['n_swir_valid']} bands",
xy=(0.99, 0.02), xycoords="axes fraction",
ha="right", va="bottom", fontsize=8, color="#666666"
)

plt.tight_layout()
if output_path:
fig.savefig(output_path, dpi=150, bbox_inches="tight")
print(f"[OK] Saved: {output_path}")
else:
plt.show()
plt.close(fig)


# ── CLI ────────────────────────────────────────────────────────────────────────

def main():
parser = argparse.ArgumentParser(
description="Plot a VNIR+SWIR pixel spectrum from a PRISMA L2C HE5 file."
)
parser.add_argument("he5_file")
parser.add_argument("row", nargs="?", type=int, default=None,
help="Pixel row (default: centre)")
parser.add_argument("col", nargs="?", type=int, default=None,
help="Pixel col (default: centre)")
parser.add_argument("-o", "--output", default=None,
help="Save figure to file (e.g. spectrum.png)")
args = parser.parse_args()

print(f"[...] Reading {args.he5_file}")
wavelengths, spectrum, meta = load_prisma(args.he5_file, args.row, args.col)

print(
f"[OK] {meta['n_vnir_valid']} VNIR + {meta['n_swir_valid']} SWIR valid bands\n"
f" Image : {meta['n_rows']} rows x {meta['n_cols']} cols\n"
f" Pixel : row={meta['row']}, col={meta['col']}\n"
f" Range : {wavelengths[0]:.1f} - {wavelengths[-1]:.1f} nm "
f"({len(wavelengths)} merged bands)"
)

plot_spectrum(wavelengths, spectrum, meta, output_path=args.output)


if __name__ == "__main__":
main()


 

 

 

 

 

 

 

 

 

 

 

martedì 17 marzo 2026

Spettri di cemento amianto

Dati derivanti da 

Wyard, C., Marion, R., & Hallot, E. (2023). Walloon roof material (WaRM) spectral library (1.0) [Data set]. Zenodo. https://doi.org/10.5281/zenodo.7414740

 

Ho estratto i due spettri di bersagli urbani relativi alla presenza di asbesto. Considerando tra negli intervalli 1324-1449 e 1810-1975 il segnale e' reso inutile dalla presenza dell'atmosfera l'unico assorbimento significativo nello SWIR risulta essere a  2324 nm

da letturatura si ha  

Crisotilo~2327 nm Vibrazione legame  
Crocidolite~2445 nmVibrazione legame  
Antofillite~2315 nmVibrazione legame  

L'assorbimento nel VNIR  a 678 nm e' riferibile ad impurita' di Fe

Da notare che il crisotilo ha una firma spettrale che assomiglia al red edge della vegetazione

 

 


queste invece sono le firme del minerale di crisotilo







 

lunedì 16 marzo 2026

Virtualbox KVM error

Cercando di usare Virtualbox mi e' uscito questo messaggio di errore

 

VT-x is being used by another hypervisor (VERR_VMX_IN_VMX_ROOT_MODE). 

VirtualBox can't operate in VMX root mode. 

Please disable the KVM kernel extension, recompile your kernel and reboot (VERR_VMX_IN_VMX_ROOT_MODE).


 

La soluzione piu' speditica e' stata rimuovere i due seguenti moduli del kernel

sudo modprobe -r kvm_intel
sudo modprobe -r kvm 

sabato 14 marzo 2026

Red Edge con Sentinel 2

Volevo migliorare un po' quanto provato qui piu' che altro per avere una migliore risoluzione spaziale. Ho provato con Sentinel 2 (20 metri di risoluzione) pagando ovviamente dal punto di vista radiometrico

 


Riferimento bibliografico 

Guyot, G., & Baret, F. (1988). Utilisation de la haute résolution spectrale pour suivre l'état des couverts végétaux. In Proceedings of the 4th International Colloquium on Spectral Signatures of Objects in Remote Sensing, Aussois, France, 18–22 January 1988 (ESA SP-287), (pp. 279–286). European Space Agency. 

 

Grafico dell'articolo originale

 

La banda 5 e banda 7 sono utilizzate per calcolare la pendenza mentre la banda 4 e banda 7 sono utilizzate per la riflettanza di soglia 

In pratica  (B4 + B7) / 2) corrisponde al punto centrale tra il valore minimo del Red ed il massimo del NIR

Viene assunto un comportamento lineare tra B5 e B6. La formula di una retta tra due punti e' 

dove

è la lunghezza d'onda che cerchiamo (
è il valore di riflettanza target ()
sono le coordinate della Banda 5
sono le coordinate della Banda 6

risolvendo per isolare l'incognita

 

La formula finale da inserire in Band Math di Snap e' 

 (35 * ((((B4 + B7) / 2) - B5) / (B6 - B5)))+705

Attenzione la banda B4 ha una risoluzione di 10 m mentre le altre hanno una risoluzione di 20 m 
 
La scala di colore e' dal viola (vegetazione piu'sana) verso il blu chiaro (vegetazione sotto stress)...il bianco e' sostanzialmente tutto cio' che non e' vegetazione 
 
Mappa
 
Facendo un transetto come fatto nella prova precedente
 
Transetto 

In questo caso sembra che la vegetazione sia maggiormente in sofferenza in corrispodenza del sito di interesse 


 

venerdì 13 marzo 2026

Olivetti Da VInci DV3 PDA funzionante

Aspettavo da tempo l'acquisizione nel mio piccolo museo. Per alcune caratteristiche seguire il link  

 



 

Indici Ensomap

Un breve riassunto degli indici utilizzati in Ensomap all'interno del plugin Enmap toolbox di QGis


 

Carbonate

1) assorbimento dopo rimozione del continuo a 2300-2400  nm 

Clay

1) assorbimento dopo rimozione del continuo a 2120-2250  nm 

Iron 

1)  assorbimento dopo rimozione del continuo a 460-620  nm
2) assorbimento dopo rimozione del continuo a 760-1050  nm 
3) Ematite (R639^2)/(B447*((B556)^3))

Materia Organica

1) 1/somma(da R400 a R700) 
2) 1/pendenza(R400..R600) 

Soil Moisture

1) (R1800-R2119)/(R1800+R2119) 
2) 1/pendenza(R1238..R2209)  

Gesso

1) (R1690-R1750)/(R1690+R1750) 


 

giovedì 12 marzo 2026

QGis e PythonPath

Ultimamente ho incasinato la Debian box e mi compare questo errore aprendo QGis...dopo funziona ma non disponibili i plugin 

la soluzione e' settare in modo esplicito la PythoPath

PYTHONPATH=/usr/lib/python3/dist-packages:$PYTHONPATH 

qgis

Spettri di Prisma

Sono ritornato a lavorare sui dati Prisma, stavolta in maniera piu' convinta Il primo problema e' trovare il software giusto. Se si ...