venerdì 27 febbraio 2026

Emit L2B

Il prodotto Emit L2B fornisce informazioni di tipo mineralogico

Per ogni pixel vengono indicati group_1_mineral_id, group_1_band_depth, group_2_mineral_id, group_2_band_depth   

Il gruppo 1 corrisponde a minerali ferrosi ed il gruppo 2 a carbonati ed argille

Viene selezionato lo spettro di laboratorio che ha il miglior fit per ogni pixel (non viene quindi fornita nessuna informazione su eventuali miscele, viene solo selezionato il minerale che risponde in modo piu' evidente dal punto di vista spettrale)

Nella mappa sottostante tramite Band Math

if (group_2_mineral_id == 227 AND group_2_band_depth > 0.05) then group_2_band_depth else NaN 

 

e' stato selezionato il minerale Calcite ed i colori corrispondono alla profondita' di picco (in modo molto esteso si puo' interpretare come abbondanza

 

In un file separato viene indicato il fit stastitico e l'incertezza sul valore della profondita' di picco 

 

Questi sono gli ID piu' comuni ma ne sono tabellati circa 300 (lista completa)

Group 1 (risposta spettrale fino a 1000 nm - VNIR)

ID (Index)Mineral NameDescription / DetailsColor Suggestion
1Hematite (Fine)Typical red soil/rock pigment; very common.Red
2Hematite (Med/Coarse)Larger grain size; found in more weathered surfaces.Dark Red
3Goethite (Fine)Yellow-brown pigment; indicator of wetter history.Yellow/Brown
4Goethite (Med/Coarse)Stronger, more mature goethite signatures.Orange
5Hematite + GoethiteA spectral mixture of both iron oxides.Magenta
6Nanophase HematiteExtremely fine grains; common in desert varnishes.Light Pink
7LimoniteA general term for unidentified iron hydroxides.Tan
8JarositeIron-sulfate; often indicates acidic/volcanic soil.Goldenrod
9MagnetiteCommon in volcanic sands (weaker VNIR signature).Black / Grey
10FerrihydriteEarly-stage iron oxide; rare but occasionally detected.Salmon

Group 2 (Risposta spettrale oltre 1000 nm - SWIR)

ID (Index)Mineral NameSpectral Feature Location
114Kaolinite (High Crystallinity)2200 nm (Doublet)
115Kaolinite (Low Crystallinity)2200 nm (Doublet)
127Muscovite2200 (Sharp)
128Illite2210nm
141Montmorillonite2210nm +1900nm
187Gypsum1750, 1900, 2200 nm
227Calcite2330 nm
218Dolomite2320nm

 

 


Emit Images

Script per la conversione delle immagini Emit (iperspettrale 60 m di risoluzione spaziale, 285 bande, aree riprese +/- 52 gradi di latitudine)

 



import xarray as xr
import numpy as np
import rasterio
from rasterio.transform import from_origin
from rasterio.warp import reproject, Resampling

# --- INPUT/OUTPUT ---
nc_file = "EMIT_L2A_RFL_001_20250609T081616_2516005_003.nc"
output_img = "emit_georef.img"

# --- OPEN DATASETS (lazy) ---
ds_ref = xr.open_dataset(nc_file)
ds_params = xr.open_dataset(nc_file, group="sensor_band_parameters")
ds_loc = xr.open_dataset(nc_file, group="location")

refl = ds_ref["reflectance"] # (downtrack, crosstrack, bands) — lazy
bands = refl.sizes["bands"]
dt = refl.sizes["downtrack"] # original downtrack axis
ct = refl.sizes["crosstrack"] # original crosstrack axis

print(f"Raw swath: {dt} downtrack × {ct} crosstrack × {bands} bands")

# --- WAVELENGTHS / FWHM ---
wavelengths = ds_params["wavelengths"].values
fwhm = ds_params["fwhm"].values if "fwhm" in ds_params else np.full_like(wavelengths, 2.5)

# --- LAT / LON ---
lat = ds_loc["lat"].values # (downtrack, crosstrack)
lon = ds_loc["lon"].values

# Diagnose orientation
print(f"lat corners: TL={lat[0,0]:.4f} TR={lat[0,-1]:.4f} BL={lat[-1,0]:.4f} BR={lat[-1,-1]:.4f}")
print(f"lon corners: TL={lon[0,0]:.4f} TR={lon[0,-1]:.4f} BL={lon[-1,0]:.4f} BR={lon[-1,-1]:.4f}")

# --- ROTATE 90° to fix east-west swath orientation ---
# np.rot90 on (downtrack, crosstrack) → (crosstrack, downtrack)
# After rotation: axis-0 = crosstrack (N-S), axis-1 = downtrack (E-W)
lat_r = np.rot90(lat) # (ct, dt)
lon_r = np.rot90(lon)

rows, cols = lat_r.shape # rows = ct, cols = dt
print(f"After rotation: {rows} rows × {cols} cols")
print(f"lat_r corners: TL={lat_r[0,0]:.4f} TR={lat_r[0,-1]:.4f} BL={lat_r[-1,0]:.4f} BR={lat_r[-1,-1]:.4f}")
print(f"lon_r corners: TL={lon_r[0,0]:.4f} TR={lon_r[0,-1]:.4f} BL={lon_r[-1,0]:.4f} BR={lon_r[-1,-1]:.4f}")

# --- TARGET REGULAR GRID ---
lat_min, lat_max = float(lat.min()), float(lat.max())
lon_min, lon_max = float(lon.min()), float(lon.max())

res = 0.000542232520256367 # ~60 m in degrees
n_rows = int(np.ceil((lat_max - lat_min) / res))
n_cols = int(np.ceil((lon_max - lon_min) / res))

print(f"Output grid: {n_cols} cols × {n_rows} rows ({n_rows*n_cols*bands*4/1e9:.2f} GB)")

# North-up destination transform: origin = top-left = (lon_min, lat_max)
dst_transform = from_origin(lon_min, lat_max, res, res)

# Source transform derived from the ROTATED lat/lon extent
src_x_res = (lon_max - lon_min) / cols
src_y_res = (lat_max - lat_min) / rows
src_transform = from_origin(lon_min, lat_max, src_x_res, src_y_res)

# --- CREATE EMPTY OUTPUT FILE ON DISK ---
with rasterio.open(
output_img, "w",
driver="ENVI",
height=n_rows, width=n_cols,
count=bands,
dtype=np.float32,
crs="EPSG:4326",
transform=dst_transform,
) as dst:
pass

# --- WARP BAND BY BAND (low memory: one band at a time) ---
with rasterio.open(output_img, "r+") as dst:
dst_band = np.empty((n_rows, n_cols), dtype=np.float32)

for b in range(bands):
# Load one band: (downtrack, crosstrack)
band_raw = refl.isel(bands=b).values.astype(np.float32)

# Rotate to match corrected orientation: (crosstrack, downtrack)
band_data = np.ascontiguousarray(np.rot90(band_raw))

dst_band[:] = -9999

reproject(
source=band_data,
destination=dst_band,
src_transform=src_transform,
src_crs="EPSG:4326",
dst_transform=dst_transform,
dst_crs="EPSG:4326",
resampling=Resampling.bilinear,
src_nodata=-9999,
dst_nodata=-9999,
)

dst.write(dst_band, b + 1)

if b % 20 == 0:
print(f" band {b+1}/{bands}")

print("Warp complete — writing HDR...")

# --- ENVI HDR ---
hdr_file = output_img.replace(".img", ".hdr")

map_info = (
f"Geographic Lat/Lon, 1, 1, "
f"{lon_min:.10f}, {lat_max:.10f}, "
f"{res:.10f}, {res:.10f}, "
f"WGS-84, units=Degrees"
)
wl_str = ", ".join(f"{w:.5f}" for w in wavelengths)
fwhm_str = ", ".join(f"{v:.5f}" for v in fwhm)
bn_str = ", ".join(f"Band {i+1}" for i in range(bands))

with open(hdr_file, "w") as f:
f.write("ENVI\n")
f.write(f"description = {{{output_img}}}\n")
f.write(f"samples = {n_cols}\n")
f.write(f"lines = {n_rows}\n")
f.write(f"bands = {bands}\n")
f.write("header offset = 0\n")
f.write("file type = ENVI Standard\n")
f.write("data type = 4\n") # 4 = float32
f.write("interleave = bsq\n")
f.write("byte order = 0\n")
f.write(f"map info = {{{map_info}}}\n")
f.write(
'coordinate system string = {GEOGCS["GCS_WGS_1984",'
'DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137.0,298.257223563]],'
'PRIMEM["Greenwich",0.0],UNIT["Degree",0.0174532925199433]]}\n'
)
f.write(f"band names = {{{bn_str}}}\n")
f.write(f"wavelength = {{{wl_str}}}\n")
f.write("wavelength units = Nanometers\n")
f.write(f"fwhm = {{{fwhm_str}}}\n")
f.write("data ignore value = -9999\n")

print("✅ Done:", output_img)
print(f" Extent: lon [{lon_min:.5f}{lon_max:.5f}] lat [{lat_min:.5f}{lat_max:.5f}]")

Casio PB-700

Un grande ingresso nel mio piccolo museo dell'informatica Casio PB-700 funzionante

 

 


giovedì 26 febbraio 2026

Da radianza a riflettanza su dati Aviris con IARR

 I dati Aviris sono distribuiti spesso in radianza ed in formato netcdf

Per trasformali in riflettanza in modo semplice tramite IARR Internal Average Relative Reflectance e per usarli in modo altrettanto tramite un formato Envi  si puo' usare il seguente script

 

Riflettanza

Radianza


 

import xarray as xr
import spectral.io.envi as envi
import numpy as np

ds_root = xr.open_dataset("dati.nc")

data_cube = ds_rad['radiance'].transpose('lines', 'samples', 'wavelength').values
mean_spectrum = np.mean(data_cube, axis=(1, 2))
reflectance = data_cube / mean_spectrum[:, None, None]


if 'wavelength' in ds_rad:
wavs = ds_rad['wavelength'].values.tolist()
elif 'wavelength' in ds_root:
wavs = ds_root['wavelength'].values.tolist()
else:
wavs = list(range(data_cube.shape[2]))

metadata = {
'description': 'AVIRIS-3 Radiance converted for SNAP',
'wavelength units': 'nanometers',
'wavelength': wavs
}

envi.save_image('aviris_radiance.hdr', data_cube, metadata=metadata, force=True)
envi.save_image('aviris_reflectance.hdr', reflectance, metadata=metadata, force=True)

print("Done! Open aviris_radiance.hdr in SNAP.")


 

mercoledì 25 febbraio 2026

Correzione atmosferica Sentinel 2 (da L1C a L2A)

Prima di tutto si parte dal download delle immagini in formato L1C da Copernicus Browser

In seguito si puo' usare Acolite o Esa Snap tramite plugin 

Acolite


Specializzato mare, fiumi acque costiere, acque torbide

Per Acolite e' preferibile la versione binaria per problemi ad impostare tutte le librerie (in particolare GDAL) per i sorgenti. Il pacchetto si puo' scaricare da qui 

 


DSF : Dark Spectrum Fitting (usa come base il pixel "piu' nero"). Si usa con bersagli come mare e laghi

RadCor : Radiative Transfer Adjacency Correction (da usare in accoppiamento con DSF) corregge pixel molto luminosi in adiacenza a pixel scuri

TACT : correzione termica

Esa Snap 

Basato su ModTran 

Per effettuare la correzione atmosferica in Esa Snap si utilizzano i plugin Sen2Cor (calibrazione terreno) e Sen2Water (calibrazione acqua)


 Una volta scaricato il plugin si deve installare il software di calcolo. Si va Tools/Manage External Tools, si clicca sul plugin e si clicca l'icona di edit (matita)

A questo punto si sceglie il tab Bundled Binaries e si clicca su installa 


 Per usare il plugin si scompatta il file zip contenente l'immagine in formato SAFE 

 Si clicca su Optical/Thematic Land Processing (o Water)/Sen2Cor Processor)/Sen2Cor

 

Soil


 

Water


Dark Object Subtraction

Vedi DSF di Acolite 

 

Non ci sono opzioni per Empirical Line ...si deve implementare a mano tramite Band Math. L'unico sistema e' tramite cliccare su un punto a spettro conosciuto ed estrarre i dati tramite Spectrum View/Copy data to clipboard e si scalano i valori di radianza in funzione della riflettanza conosciuta banda per banda (oppure assumendo un comportamento lineare si clicca sul pixel  piu' chiaro e quello piu' scuro e si trova la regressione lineare per tutte le bande) 

martedì 24 febbraio 2026

AsteroidOS TicWatch Pro 2018 wf12096

Linux al polso.. dopo quasi 8 anni riprovo AsteroidOs e devo dire che non e' niente male




Per sincronizzare con il telefono si usa la app che AsteroidOs Syncsi trova su F-Droid. Al momento di fare l'onboarding la app rimaneva bloccata per i permessi di notifica...si deve cercare il menu della foto sottostante ed abilitare esplicitamente a meno


 

Kernel Panic QrCode

 In tanti anni ho visto qualche kernel panic, ma in questo formato non mi era mai successo    la cosa curiosa che al riavvio successivo ness...