giovedì 24 settembre 2026

Entropia in telerilevamento

Questa e' un risorsa a cui aggrapparsi quando non c'e' niente nell'informazione spettrale che possa aiutare

In pratica si usa l'entropia dell'immagine per distinguere nella tessitura dell'immagine

 



 

#!/usr/bin/env python3
"""
texture_features.py

Calcola mappe di texture (entropia locale + feature GLCM: contrast, homogeneity,
correlation, energy) da un'immagine grayscale o RGB (es. TIFF), per evidenziare
differenze di struttura spaziale tra oggetti di colore simile.

Pipeline:
    1. Legge l'immagine (TIFF/PNG/JPEG...). Se RGB, converte in luminanza
       (rgb2gray) per isolare la struttura spaziale dal colore.
    2. Normalizza a uint8.
    3. Calcola entropia locale (skimage.filters.rank.entropy) a chunk di righe.
    4. Calcola feature GLCM (contrast, homogeneity, correlation, energy) su
       finestra scorrevole, mediando su 4 angoli (0, 45, 90, 135 gradi) per
       essere invariante all'orientamento della texture.
    5. Salva ogni mappa come GeoTIFF (o TIFF semplice se l'input non ha georef).

Uso:
    python texture_features.py 264150.TIF --out_dir ./texture_out \
        --entropy_radius 5 --glcm_window 15 --glcm_step 4

    --glcm_step > 1 calcola il GLCM ogni N pixel e poi fa upsampling (nearest)
    per velocizzare — il GLCM è l'operazione più costosa dello script.

Dipendenze:
    pip install scikit-image numpy tifffile rasterio  # rasterio opzionale, solo per georef
"""

import argparse
import os
import numpy as np

from skimage.io import imread, imsave
from skimage.color import rgb2gray
from skimage.util import img_as_ubyte
from skimage.filters.rank import entropy as rank_entropy
from skimage.morphology import disk
from skimage.feature import graycomatrix, graycoprops


def load_grayscale(path, verbose=True):
    """Carica l'immagine e la converte in luminanza uint8 se RGB."""
    img = imread(path)
    if verbose:
        print(f"Immagine caricata: shape={img.shape}, dtype={img.dtype}")

    if img.ndim == 3:
        # scarta eventuale canale alpha
        rgb = img[:, :, :3]
        gray = rgb2gray(rgb)
        if verbose:
            print("Immagine RGB -> convertita in luminanza (rgb2gray) per isolare la struttura dal colore.")
    else:
        gray = img.astype(np.float64)

    gray = np.nan_to_num(gray, nan=0.0)
    gmin, gmax = gray.min(), gray.max()
    if gmax - gmin < 1e-12:
        return np.zeros_like(gray, dtype=np.uint8)
    gray_norm = (gray - gmin) / (gmax - gmin)
    return img_as_ubyte(gray_norm)


def compute_entropy_map(img_u8, radius, chunk_rows=512, verbose=True):
    """Entropia locale a chunk di righe, con overlap = radius."""
    n_rows, n_cols = img_u8.shape
    out = np.zeros((n_rows, n_cols), dtype=np.float32)
    footprint = disk(radius)

    for start in range(0, n_rows, chunk_rows):
        end = min(start + chunk_rows, n_rows)
        pad_top = min(radius, start)
        pad_bottom = min(radius, n_rows - end)

        chunk = img_u8[start - pad_top: end + pad_bottom, :]
        ent_chunk = rank_entropy(chunk, footprint)
        out[start:end, :] = ent_chunk[pad_top: pad_top + (end - start), :]

        if verbose:
            print(f"  entropia: righe {start}-{end}/{n_rows} completate")

    return out


def compute_glcm_maps(img_u8, window, step=4, levels=32, verbose=True):
    """
    Calcola contrast/homogeneity/correlation/energy con GLCM su finestra
    scorrevole (mediata su 4 angoli), campionando ogni `step` pixel e poi
    facendo upsampling nearest per velocizzare. Restituisce un dict di mappe
    2D della stessa dimensione dell'input.
    """
    n_rows, n_cols = img_u8.shape
    half = window // 2
    angles = [0, np.pi / 4, np.pi / 2, 3 * np.pi / 4]
    props = ['contrast', 'homogeneity', 'correlation', 'energy']

    rows_idx = list(range(half, n_rows - half, step))
    cols_idx = list(range(half, n_cols - half, step))

    small_maps = {p: np.zeros((len(rows_idx), len(cols_idx)), dtype=np.float32) for p in props}

    total = len(rows_idx)
    for ri, r in enumerate(rows_idx):
        for ci, c in enumerate(cols_idx):
            patch = img_u8[r - half:r + half, c - half:c + half]
            patch_q = (patch.astype(np.float32) / 255 * (levels - 1)).astype(np.uint8)
            glcm = graycomatrix(patch_q, distances=[1], angles=angles,
                                 levels=levels, symmetric=True, normed=True)
            for p in props:
                small_maps[p][ri, ci] = graycoprops(glcm, p).mean()
        if verbose and ri % max(1, total // 10) == 0:
            print(f"  GLCM: riga {ri}/{total}")

    # upsampling nearest alla dimensione originale
    from skimage.transform import resize
    full_maps = {}
    for p in props:
        full_maps[p] = resize(small_maps[p], (n_rows, n_cols), order=0,
                               preserve_range=True, anti_aliasing=False).astype(np.float32)
    return full_maps


def save_map(arr, path, verbose=True):
    """Salva una mappa float32 come TIFF a 32 bit."""
    imsave(path, arr.astype(np.float32))
    if verbose:
        print(f"Salvato: {path}")


def main():
    parser = argparse.ArgumentParser(description="Feature di texture (entropia + GLCM) da immagine grayscale/RGB.")
    parser.add_argument("image_path", help="Path all'immagine (es. 264150.TIF)")
    parser.add_argument("--out_dir", default="./texture_out", help="Cartella di output")
    parser.add_argument("--entropy_radius", type=int, default=5, help="Raggio disk() per entropia locale")
    parser.add_argument("--glcm_window", type=int, default=15, help="Dimensione finestra quadrata per GLCM")
    parser.add_argument("--glcm_step", type=int, default=4,
                         help="Passo di campionamento per il GLCM (>1 = piu' veloce, poi upsampling)")
    parser.add_argument("--glcm_levels", type=int, default=32, help="Numero di livelli di quantizzazione per GLCM")
    parser.add_argument("--chunk_rows", type=int, default=512, help="Righe per chunk nel calcolo dell'entropia")
    args = parser.parse_args()

    os.makedirs(args.out_dir, exist_ok=True)

    print(f"Caricamento immagine: {args.image_path}")
    img_u8 = load_grayscale(args.image_path)

    gray_path = os.path.join(args.out_dir, "grayscale_input.tif")
    save_map(img_u8.astype(np.float32), gray_path)

    print(f"\nCalcolo entropia locale (raggio={args.entropy_radius}) ...")
    ent_map = compute_entropy_map(img_u8, args.entropy_radius, chunk_rows=args.chunk_rows)
    save_map(ent_map, os.path.join(args.out_dir, "entropy.tif"))

    print(f"\nCalcolo feature GLCM (finestra={args.glcm_window}, step={args.glcm_step}) ...")
    glcm_maps = compute_glcm_maps(img_u8, args.glcm_window, step=args.glcm_step, levels=args.glcm_levels)
    for name, arr in glcm_maps.items():
        save_map(arr, os.path.join(args.out_dir, f"glcm_{name}.tif"))

    print("\nCompletato. Mappe generate:")
    print("  - entropy.tif           (disordine generale)")
    print("  - glcm_contrast.tif     (differenze locali di intensita', bordi netti)")
    print("  - glcm_homogeneity.tif  (uniformita' locale)")
    print("  - glcm_correlation.tif  (pattern direzionali/periodici)")
    print("  - glcm_energy.tif       (regolarita'/ripetitivita')")
    print("\nConfronta queste mappe negli stessi punti per separare oggetti con colore simile ma struttura diversa.")


if __name__ == "__main__":
    main()

 

martedì 22 settembre 2026

Microsoft office a dischetti

 Una nuova acquisizione per il mio piccolo museo dell'informatica. Microsoft Office ed Acces con installazione a dischetti

 




 

lunedì 21 settembre 2026

USGS Tetracorder Docker

Non esistono metodi banali per installare Tetracorder (versione moderna) ma una versione comoda e' costituita da un docker non ufficiale

 


git clone https://github.com/jl-0/tetracorder-lite.git

cd tetracorder-lite

 prima di fare la build si deve modificare il file Containerfile aggiungendo la riga evidenziata in giallo

RUN cd tetracorder &&\
    sed -i "398,416 s/^/#/" AAA.INSTALL.spectroscopy-os-setup-linux.sh &&\
    sed -i "231,254 s/^/#/" AAA.INSTALL.spectroscopy-os-setup-linux.sh &&\
    yes "y" | bash AAA.INSTALL.spectroscopy-os-setup-linux.sh install &&\
    cd tetracorder &&\
    chmod +x checkmultmapcompilemode &&\
    make install &&\
    sed -i "137,140 s/^/#/" multmap.h &&\
    sed -i "144,147 s/^#//" multmap.h &&\
    make installsingle 

a questo punto si fa la build

docker build --platform linux/amd64 -f Containerfile -t tetracorder-lite . 

e si creano dei folder di lavoro

mkdir -p ~/tetracorder/input ~/tetracorder/output 

per controllare che tutto sia a posto si puo' lanciare 

docker run --rm tetracorder-lite tetrapy --help
docker run --rm tetracorder-lite tetrapy run --help

a questo punto siamo pronti a lanciare una prima elaborazione.Il problema e' che  Tetracorder accetta immagini in formato Envi mentre le immagini Emit sono distribuite in formato NetCDF (si devono scarivare sia i dati RFL di riflettanza che RFLUNCERT dell'incertezza della riflettanza

Per la conversione si possono le emit utilities 

git clone https://github.com/nasa/emit-utils.git
cd emit-utils
python emit_utils/reformat.py /path/to/EMIT_L2A_RFL_xxx.nc /path/to/output_envi/ --orthorectify 

funzionano ma non sono riuscito ad installare su Debian pip install xarray netCDF4 rioxarray earthaccess ovvero i modulo per orthorectify...si puo' fare a meno per questa prova

cd ~/emit-utils
python -m emit_utils.reformat ./emit_utils/EMIT_L2A_RFL_001_20260825T085423_2623706_045.nc /home/luca/tetracorder/input/ --orthorectify

arrivati a questo punto si puo' lanciare il processing  

docker run --rm \
  -v ~/tetracorder/input:/data \
  -v ~/tetracorder/output:/output \
  tetracorder-lite \
  tetrapy run config.yml \
    --data.rfl /data/EMIT_L2A_RFL_001_20260825T085423_2623706_045_reflectance \
    --data.rfluncert /data/EMIT_L2A_RFLUNCERT_001_20260825T085423_2623706_045_reflectance_uncertainty 

 

al termine del processing avremo una serie di file tipo la seguente dove

  • group.1um, group.1.5um-broad, group.2um, group.2um-broad — minerali (argille, carbonati, solfati, ecc., a seconda della banda di assorbimento)
  • group.ree, group.ree_g21, group.ree_samar — terre rare (rare earth elements)
  • group.ch4gas-2.3um, group.co2gas-2um — gas (metano, CO2)
  • group.veg — vegetazione
  • case.ep-cal-chl — epidoto/calcite/clorite
  • case.red-edge — red edge della vegetazione
  • case.veg.type — tipologia di vegetazione
  •   

    .
    ./test
    ./test/aggregate
    ./test/aggregate/agg.nc
    ./test/aggregate/config.yml
    ./test/aggregate/agg-uncert.nc
    ./test/aggregate/tetrapy.log
    ./test/tetracorder
    ./test/tetracorder/AAAA.license.txt
    ./test/tetracorder/group.1.5um-broad
    ./test/tetracorder/group.1.5um-broad/g4-fe2+generic_vbroad_br20.fit.gz
    ./test/tetracorder/group.1.5um-broad/olivine_fo89_gds70.d.depth.gz
    ./test/tetracorder/group.1.5um-broad/snow-ice-10mm+0.3water-273k.depth.gz
    ./test/tetracorder/group.1.5um-broad/vegetation1.fit.gz
    ./test/tetracorder/group.1.5um-broad/snow-ice-3mm+0.2water-273k.depth.gz
    ./test/tetracorder/group.1.5um-broad/water.high.chlorophyll2.depth.gz.hdr
    ./test/tetracorder/group.1.5um-broad/olivine_fo18_ki3377.fd.gz
    ./test/tetracorder/group.1.5um-broad/water-20C-100cm-path+chlrph-a.fit.gz.hdr
    ./test/tetracorder/group.1.5um-broad/snow-ice-3mm+0.1water-273k.fit.gz
    ./test/tetracorder/group.1.5um-broad/water+mont300gpl.fd.gz.hdr
    ./test/tetracorder/group.1.5um-broad/snow-ice-3mm+0.2water-273k.fd.gz.hdr
    ./test/tetracorder/group.1.5um-broad/water+mont50gpl.fd.gz.hdr
    ./test/tetracorder/group.1.5um-broad/olivine_fo60_ki3189.fit.gz
    ./test/tetracorder/group.1.5um-broad/water-20C-300cm-path+chlrph-g.depth.gz.hdr
    ./test/tetracorder/group.1.5um-broad/water+mont0.5gpl.fit.gz.hdr
    ./test/tetracorder/group.1.5um-broad/snow.melting16+0.5veg.depth.gz.hdr
    ./test/tetracorder/group.1.5um-broad/anorthite_hs201.3b.fit.gz.hdr
    ./test/tetracorder/group.1.5um-broad/water-20C-100cm-path+chlrph-a.depth.gz.hdr
    ./test/tetracorder/group.1.5um-broad/water-20C-10cm-path-model.depth.gz
    ./test/tetracorder/group.1.5um-broad/water-20C-100cm-path-model.fit.gz
    ./test/tetracorder/group.1.5um-broad/snow.melting.8.fd.gz
    ./test/tetracorder/group.1.5um-broad/snow-ice-10mm-273k.fd.gz
    ./test/tetracorder/group.1.5um-broad/snow.slush.9.depth.gz
    ./test/tetracorder/group.1.5um-broad/water+mont0.5gpl.fd.gz
    ./test/tetracorder/group.1.5um-broad/olivine_fo66_ki3054.depth.gz.hdr
    ./test/tetracorder/group.1.5um-broad/olivine_fo60_ki3189.fit.gz.hdr
    ./test/tetracorder/group.1.5um-broad/g4-fe2+generic_brd.br22c_actinolite.fit.gz
    ./test/tetracorder/group.1.5um-broad/vegetation1.fd.gz.hdr
    ./test/tetracorder/group.1.5um-broad/water+mont50gpl+0.5veg.fit.gz
    ./test/tetracorder/group.1.5um-broad/water-20C-30cm-path-model.fit.gz
    ./test/tetracorder/group.1.5um-broad/anorthite_hs201.3b.fd.gz
    ./test/tetracorder/group.1.5um-broad/snow-ice-3mm+0.3water-273k.fit.gz.hdr
    ./test/tetracorder/group.1.5um-broad/snow-ice-3mm+0.1water-273k.fd.gz.hdr
    ./test/tetracorder/group.1.5um-broad/rutile_hs126.3b.fit.gz.hdr
    ./test/tetracorder/group.1.5um-broad/snow-ice-3mm+0.1water-273k.fd.gz
    ./test/tetracorder/group.1.5um-broad/water-20C-30cm-path-model.depth.gz
    ./test/tetracorder/group.1.5um-broad/snow.melting16+0.5veg.fit.gz.hdr
    ./test/tetracorder/group.1.5um-broad/g4-fe2+generic_nrw.cummingtonite.fd.gz
    ./test/tetracorder/group.1.5um-broad/vegetation.dry+green.depth.gz.hdr
    ./test/tetracorder/group.1.5um-broad/water-20C-300cm-path+chlrph-g.fd.gz.hdr
    ./test/tetracorder/group.1.5um-broad/olivine_fo66_ki3054.depth.gz
    ./test/tetracorder/group.1.5um-broad/snow-ice-1mm-273k.fd.gz
    ./test/tetracorder/group.1.5um-broad/water+mont16.5gpl.fd.gz.hdr
    ./test/tetracorder/group.1.5um-broad/g4-fe2+generic_chlorite.clinochlor.fit.gz.hdr
    ./test/tetracorder/group.1.5um-broad/snow-ice-3mm-273k.fit.gz.hdr
    ./test/tetracorder/group.1.5um-broad/g4-fe2+generic_vbroad_br20.fd.gz.hdr
    ./test/tetracorder/group.1.5um-broad/water-20C-100cm-path+chlrph-g.fit.gz
    ./test/tetracorder/group.1.5um-broad/snow-ice-3mm+0.3water-273k.fd.gz.hdr
    ./test/tetracorder/group.1.5um-broad/microcline_hs103.3b.fd.gz.hdr
    ./test/tetracorder/group.1.5um-broad/snow-ice-1mm-273k.depth.gz
    ./test/tetracorder/group.1.5um-broad/snow.slush.9.fd.gz.hdr
    ./test/tetracorder/group.1.5um-broad/bytownite_hs106.3b.depth.gz
    ./test/tetracorder/group.1.5um-broad/water-20C-10cm-path+chlrph-g.depth.gz
    ./test/tetracorder/group.1.5um-broad/organic_dry_long_grass-thick.depth.gz.hdr
    ./test/tetracorder/group.1.5um-broad/water-20C-30cm-path-model.fd.gz
    ./test/tetracorder/group.1.5um-broad/water+mont50gpl+0.5veg.fd.gz.hdr
    ./test/tetracorder/group.1.5um-broad/vegetation.dry+green.fd.gz
    ./test/tetracorder/group.1.5um-broad/water.turquoise-shallow.fd.gz
    ./test/tetracorder/group.1.5um-broad/g4-fe2+generic_chlor+muscphy.fit.gz
    ./test/tetracorder/group.1.5um-broad/water-20C-100cm-path+chlrph-a.fit.gz
    ./test/tetracorder/group.1.5um-broad/water.high.chlorophyll2.fit.gz
    ./test/tetracorder/group.1.5um-broad/snow-ice-3mm+0.1water-273k.depth.gz.hdr
    ./test/tetracorder/group.1.5um-broad/water+mont50gpl.fd.gz
    ./test/tetracorder/group.1.5um-broad/bytownite_hs106.3b.depth.gz.hdr
    ./test/tetracorder/group.1.5um-broad/sphalerite_s26-341_1.5um.fd.gz.hdr
    ./test/tetracorder/group.1.5um-broad/anorthite_hs201.3b.fit.gz
    ./test/tetracorder/group.1.5um-broad/water+mont5.01gpl.fd.gz
    ./test/tetracorder/group.1.5um-broad/snow.melting.1a.depth.gz
    ./test/tetracorder/group.1.5um-broad/water+mont100gpl.fit.gz.hdr
    ./test/tetracorder/group.1.5um-broad/snow-ice-10mm+0.3water-273k.fit.gz
    ./test/tetracorder/group.1.5um-broad/snow.melting.8.fit.gz.hdr
    ./test/tetracorder/group.1.5um-broad/olivine_fo18_ki3377.depth.gz
    ./test/tetracorder/group.1.5um-broad/snow.slush.9.fit.gz
    ./test/tetracorder/group.1.5um-broad/rutile_hs126.3b.depth.gz
    ./test/tetracorder/group.1.5um-broad/vegetation.dry+green.fd.gz.hdr
    ./test/tetracorder/group.1.5um-broad/sphalerite_s26-341_1.5um.fit.gz
    ./test/tetracorder/group.1.5um-broad/olivine_fo89_gds70.a.fit.gz
    ./test/tetracorder/group.1.5um-broad/anorthite_hs201.3b.fd.gz.hdr
    ./test/tetracorder/group.1.5um-broad/snow.melting.3.depth.gz
    ./test/tetracorder/group.1.5um-broad/snow.melting.1a.fd.gz.hdr
    ./test/tetracorder/group.1.5um-broad/water+mont16.5gpl.depth.gz
    ./test/tetracorder/group.1.5um-broad/snow.melting.3.fit.gz
    ./test/tetracorder/group.1.5um-broad/water+mont5.01gpl.fit.gz.hdr
    ./test/tetracorder/group.1.5um-broad/organic_dry_long_grass-thick.fit.gz.hdr
    ./test/tetracorder/group.1.5um-broad/snow-ice-3mm+0.3water-273k.fd.gz
    ./test/tetracorder/group.1.5um-broad/g4-fe2+generic_brd.br5a_actinolite.fit.gz
    ./test/tetracorder/group.1.5um-broad/rutile_hs126.3b.fit.gz
    ./test/tetracorder/group.1.5um-broad/water.low.chlorophyll-blue1.depth.gz
    ./test/tetracorder/group.1.5um-broad/snow-ice-3mm+0.3water-273k.depth.gz.hdr
    ./test/tetracorder/group.1.5um-broad/organic_white_pvc_pipe.fd.gz.hdr
    ./test/tetracorder/group.1.5um-broad/g4-fe2+generic_chlor+muscphy.fd.gz.hdr
    ./test/tetracorder/group.1.5um-broad/olivine_fo91_gds71.b.fd.gz
    ./test/tetracorder/group.1.5um-broad/snow.slush.16.fit.gz
    ./test/tetracorder/group.1.5um-broad/olivine_fo89_gds70.d.fit.gz
    ./test/tetracorder/group.1.5um-broad/snow.and.ice-170k.fit.gz.hdr
    ./test/tetracorder/group.1.5um-broad/water-20C-100cm-path+chlrph-g.fd.gz
    ./test/tetracorder/group.1.5um-broad/water.low.chlorophyll-blue1.fd.gz
    ./test/tetracorder/group.1.5um-broad/snow.slush.16.fit.gz.hdr
    ./test/tetracorder/group.1.5um-broad/snow.slush.16.depth.gz
    ./test/tetracorder/group.1.5um-broad/snow.melting9+0.5veg.fd.gz
    ./test/tetracorder/group.1.5um-broad/water-20C-100cm-path+chlrph-a.fd.gz.hdr
    ./test/tetracorder/group.1.5um-broad/olivine_fo66_ki3054.fd.gz
    ./test/tetracorder/group.1.5um-broad/water+mont100gpl+0.5veg.depth.gz
    ./test/tetracorder/group.1.5um-broad/sphalerite_s26-341_1.5um.depth.gz.hdr
    ./test/tetracorder/group.1.5um-broad/snow.melting9+0.5veg.fd.gz.hdr
    ./test/tetracorder/group.1.5um-broad/organic_green_plastic_tarp.fit.gz
    ./test/tetracorder/group.1.5um-broad/vegetation.dry+green.fit.gz.hdr
    ./test/tetracorder/group.1.5um-broad/snow-ice-10mm+0.3water-273k.fd.gz.hdr
    ./test/tetracorder/group.1.5um-broad/snow-ice-3mm-273k.depth.gz
    ./test/tetracorder/group.1.5um-broad/snow.and.ice-248k.fit.gz.hdr
    ./test/tetracorder/group.1.5um-broad/g4-fe2+generic_brd.br5a_actinolite.fd.gz.hdr
    ./test/tetracorder/group.1.5um-broad/g4-fe2+generic_brd.br5a_actinolite.fit.gz.hdr
    ./test/tetracorder/group.1.5um-broad/water+mont16.5gpl.fd.gz
    ./test/tetracorder/group.1.5um-broad/water+mont100gpl.fd.gz
    ./test/tetracorder/group.1.5um-broad/g4-fe2+generic_vbroad_br20.depth.gz.hdr
    ./test/tetracorder/group.1.5um-broad/water-20C-30cm-path-model.fd.gz.hdr
    ./test/tetracorder/group.1.5um-broad/olivine_fo18_ki3377.depth.gz.hdr
    ./test/tetracorder/group.1.5um-broad/rutile_hs126.3b.fd.gz.hdr
    ./test/tetracorder/group.1.5um-broad/g4-fe2+generic_nrw.cummingtonite.depth.gz.hdr
    ./test/tetracorder/group.1.5um-broad/g4-fe2+generic_vbroad_br20.depth.gz
    ./test/tetracorder/group.1.5um-broad/snow.melting.8.fit.gz
    ./test/tetracorder/group.1.5um-broad/water.low.chlorophyll.fit.gz
    ./test/tetracorder/group.1.5um-broad/water.turquoise-shallow.fit.gz
    ./test/tetracorder/group.1.5um-broad/olivine_fo80_hs285.depth.gz.hdr
    ./test/tetracorder/group.1.5um-broad/olivine_fo89_gds70.c.fit.gz.hdr
    ./test/tetracorder/group.1.5um-broad/snow.melting9+0.5veg.depth.gz.hdr
    ./test/tetracorder/group.1.5um-broad/water+mont1.67gpl.fd.gz
    ./test/tetracorder/group.1.5um-broad/water+mont5.01gpl.fd.gz.hdr
    ./test/tetracorder/group.1.5um-broad/snow.melting16+0.5veg.fit.gz
    ./test/tetracorder/group.1.5um-broad/water+mont1.67gpl.fit.gz
    ./test/tetracorder/group.1.5um-broad/water.red.algae.fd.gz.hdr
    ./test/tetracorder/group.1.5um-broad/water+mont1.67gpl.depth.gz
    ./test/tetracorder/group.1.5um-broad/water-20C-100cm-path+chlrph-g.fit.gz.hdr
    ./test/tetracorder/group.1.5um-broad/snow-ice-3mm+0.2water-273k.depth.gz.hdr
    ./test/tetracorder/group.1.5um-broad/olivine_fo66_ki3054.fit.gz
    ./test/tetracorder/group.1.5um-broad/water.high.chlorophyll.fd.gz
    ./test/tetracorder/group.1.5um-broad/water+mont0.5gpl.fd.gz.hdr
    ./test/tetracorder/group.1.5um-broad/magnetite_hs195.3b.or.olivine.fd.gz
    ./test/tetracorder/group.1.5um-broad/water-20C-300cm-path+chlrph-g.fd.gz
    ./test/tetracorder/group.1.5um-broad/water+mont100gpl.fit.gz
    ./test/tetracorder/group.1.5um-broad/snow.melting.1a.fd.gz
    ./test/tetracorder/group.1.5um-broad/water.low.chlorophyll-blue1.fd.gz.hdr
    ./test/tetracorder/group.1.5um-broad/olivine_fo18_ki3377.fit.gz
    ./test/tetracorder/group.1.5um-broad/olivine_fo60_ki3189.fd.gz.hdr
    ./test/tetracorder/group.1.5um-broad/magnetite_hs195.3b.or.olivine.fit.gz
    ./test/tetracorder/group.1.5um-broad/olivine_fo18_ki3377.fit.gz.hdr
    ./test/tetracorder/group.1.5um-broad/water+mont100gpl+0.5veg.fd.gz.hdr
    ./test/tetracorder/group.1.5um-broad/g4-fe2+generic_chlorite.clinochlor.fd.gz.hdr
    ./test/tetracorder/group.1.5um-broad/water+mont100gpl.depth.gz.hdr
    ./test/tetracorder/group.1.5um-broad/g4-fe2+generic_nrw.cummingtonite.fd.gz.hdr
    ./test/tetracorder/group.1.5um-broad/snow.slush.16.fd.gz
    ./test/tetracorder/group.1.5um-broad/water.high.chlorophyll.fd.gz.hdr
    ./test/tetracorder/group.1.5um-broad/snow-ice-10mm+0.3water-273k.fd.gz
    ./test/tetracorder/group.1.5um-broad/organic_green_plastic_tarp.fit.gz.hdr
    ./test/tetracorder/group.1.5um-broad/water+mont0.5gpl.fit.gz
    ./test/tetracorder/group.1.5um-broad/olivine_fo91_gds71.b.fd.gz.hdr
    ./test/tetracorder/group.1.5um-broad/water+mont50gpl.fit.gz.hdr
    ./test/tetracorder/group.1.5um-broad/snow.and.ice-248k.depth.gz
    ./test/tetracorder/group.1.5um-broad/organic_green_plastic_tarp.fd.gz.hdr
    ./test/tetracorder/group.1.5um-broad/bytownite_hs106.3b.fd.gz
    ./test/tetracorder/group.1.5um-broad/olivine_fo80_hs285.fit.gz
    ./test/tetracorder/group.1.5um-broad/water.high.chlorophyll2.fit.gz.hdr
    ./test/tetracorder/group.1.5um-broad/water-20C-300cm-path+chlrph-g.depth.gz
    ./test/tetracorder/group.1.5um-broad/vegetation1.depth.gz.hdr
    ./test/tetracorder/group.1.5um-broad/g4-fe2+generic_nrw.cummingtonite.fit.gz
    ./test/tetracorder/group.1.5um-broad/olivine_fo80_hs285.fit.gz.hdr
    ./test/tetracorder/group.1.5um-broad/water.red.algae.fit.gz.hdr
    ./test/tetracorder/group.1.5um-broad/snow.slush.9.fd.gz
    ./test/tetracorder/group.1.5um-broad/water+mont1.67gpl.fit.gz.hdr
    ./test/tetracorder/group.1.5um-broad/snow-ice-3mm+0.1water-273k.fit.gz.hdr
    ./test/tetracorder/group.1.5um-broad/water.turquoise-shallow.depth.gz.hdr
    ./test/tetracorder/group.1.5um-broad/snow.and.ice-170k.depth.gz
    ./test/tetracorder/group.1.5um-broad/g4-fe2+generic_chlor+muscphy.depth.gz.hdr
    ./test/tetracorder/group.1.5um-broad/vegetation.dry+green.fit.gz
    ./test/tetracorder/group.1.5um-broad/water+mont300gpl.fit.gz
    ./test/tetracorder/group.1.5um-broad/snow-ice-3mm+0.2water-273k.fit.gz.hdr
    ./test/tetracorder/group.1.5um-broad/sphalerite_s26-341_1.5um.fd.gz
    ./test/tetracorder/group.1.5um-broad/snow.melting.8.depth.gz
    ./test/tetracorder/group.1.5um-broad/g4-fe2+generic_chlor+muscphy.fd.gz
    ./test/tetracorder/group.1.5um-broad/g4-fe2+generic_brd.br22c_actinolite.fd.gz.hdr
    ./test/tetracorder/group.1.5um-broad/water+mont50gpl.fit.gz
    ./test/tetracorder/group.1.5um-broad/g4-fe2+generic_chlor+muscphy.depth.gz
    ./test/tetracorder/group.1.5um-broad/bytownite_hs106.3b.fit.gz
    ./test/tetracorder/group.1.5um-broad/water.low.chlorophyll.fd.gz
    ./test/tetracorder/group.1.5um-broad/magnetite_hs195.3b.or.olivine.fd.gz.hdr
    ./test/tetracorder/group.1.5um-broad/snow-ice-10mm-273k.depth.gz.hdr
    ./test/tetracorder/group.1.5um-broad/water-20C-10cm-path-model.depth.gz.hdr
    ./test/tetracorder/group.1.5um-broad/water-20C-100cm-path-model.fit.gz.hdr
    ./test/tetracorder/group.1.5um-broad/water.turquoise-shallow.fit.gz.hdr
    ./test/tetracorder/group.1.5um-broad/water.low.chlorophyll.fd.gz.hdr
    ./test/tetracorder/group.1.5um-broad/snow-ice-10mm-273k.fit.gz
    ./test/tetracorder/group.1.5um-broad/water-20C-100cm-path-model.fd.gz.hdr
    ./test/tetracorder/group.1.5um-broad/water.low.chlorophyll-turquoise.depth.gz
    ./test/tetracorder/group.1.5um-broad/water.red.algae.fd.gz
    ./test/tetracorder/group.1.5um-broad/olivine_fo89_gds70.d.depth.gz.hdr
    ./test/tetracorder/group.1.5um-broad/snow-ice-3mm+0.3water-273k.depth.gz
    ./test/tetracorder/group.1.5um-broad/water+mont100gpl+0.5veg.fit.gz.hdr
    ./test/tetracorder/group.1.5um-broad/water.red.algae.fit.gz
    ./test/tetracorder/group.1.5um-broad/water.high.chlorophyll.depth.gz
    ./test/tetracorder/group.1.5um-broad/organic_white_pvc_pipe.fd.gz
    ./test/tetracorder/group.1.5um-broad/olivine_fo89_gds70.a.fd.gz
    ./test/tetracorder/group.1.5um-broad/water-20C-100cm-path+chlrph-a.fd.gz
    ./test/tetracorder/group.1.5um-broad/snow-ice-10mm-273k.fd.gz.hdr
    ./test/tetracorder/group.1.5um-broad/g4-fe2+generic_vbroad_br20.fit.gz.hdr
    ./test/tetracorder/group.1.5um-broad/olivine_fo89_gds70.c.depth.gz.hdr
    ./test/tetracorder/group.1.5um-broad/olivine_fo89_gds70.d.fd.gz.hdr
    ./test/tetracorder/group.1.5um-broad/anorthite_hs201.3b.depth.gz
    ./test/tetracorder/group.1.5um-broad/water.turquoise-shallow.depth.gz
    ./test/tetracorder/group.1.5um-broad/snow.melting.1a.fit.gz.hdr
    ./test/tetracorder/group.1.5um-broad/g4-fe2+generic_brd.br5a_actinolite.fd.gz
    ./test/tetracorder/group.1.5um-broad/olivine_fo91_gds71.b.depth.gz
    ./test/tetracorder/group.1.5um-broad/microcline_hs103.3b.depth.gz.hdr
    ./test/tetracorder/group.1.5um-broad/organic_white_pvc_pipe.depth.gz
    ./test/tetracorder/group.1.5um-broad/rutile_hs126.3b.depth.gz.hdr
    ./test/tetracorder/group.1.5um-broad/water-20C-100cm-path+chlrph-a.depth.gz
    ./test/tetracorder/group.1.5um-broad/snow.and.ice-170k.fd.gz
    ./test/tetracorder/group.1.5um-broad/water.turquoise-shallow.fd.gz.hdr
    ./test/tetracorder/group.1.5um-broad/olivine_fo89_gds70.d.fit.gz.hdr
    ./test/tetracorder/group.1.5um-broad/g4-fe2+generic_brd.br22c_actinolite.depth.gz.hdr
    ./test/tetracorder/group.1.5um-broad/olivine_fo89_gds70.c.depth.gz
    ./test/tetracorder/group.1.5um-broad/water+mont300gpl.depth.gz.hdr
    ./test/tetracorder/group.1.5um-broad/water+mont0.5gpl.depth.gz

     

    rimane il problema della visualizzazione  

    mkdir -p ~/tetracorder_out
    python tetracorder_iron_oxides.py \
        --group-dir group.2um \
        --filter "kaolgrp_|kaol|smectite_|micagrp_illite|micagrp_vermiculite|chlorite|palygorskite|sepiolite|saponite|hectorite|dickite|nacrite|halloysite|pyroph" \
        --output ~/tetracorder_out/argille_map.png \
        --geotiff ~/tetracorder_out/argille_map.tif \
        --title "Minerali argillosi" 

     

    #!/usr/bin/env python3
    """
    tetracorder_visualize.py
    =========================

    Visualizza una mappa di classificazione prodotta da USGS Tetracorder
    (es. results.cluster, results.cluster.sorted, results.group.X, ...)
    colorando ogni classe secondo il file color.keys prodotto dallo stesso run,
    e generando una legenda con i nomi dei materiali.

    USO
    ---
    python tetracorder_visualize.py \
    --cluster /path/results.cluster.sorted \
    --colorkeys /path/color.keys \
    --output mappa_materiali.png

    Se il file .hdr (ENVI) ha lo stesso nome + ".hdr" o si chiama
    "<cluster>.hdr", viene individuato automaticamente. In alternativa
    si può passare esplicitamente con --hdr.

    DIPENDENZE
    ----------
    pip install numpy matplotlib
    # opzionale ma consigliato (lettura ENVI con georeferenziazione):
    pip install rasterio

    NOTE SUL PARSING DI color.keys
    -------------------------------
    Il formato del file color.keys di Tetracorder non è uno standard fisso:
    varia leggermente tra versioni. Questo script prova diversi pattern
    comuni (vedi funzione `parse_color_keys`). Se il parsing fallisce o i
    colori/nomi non tornano, lancia lo script con --debug-colorkeys per
    vedere riga per riga cosa viene interpretato, e poi mandami un estratto
    delle prime ~10 righe del tuo file color.keys così affino il parser.
    """

    import argparse
    import re
    import sys
    from pathlib import Path

    import numpy as np
    import matplotlib.pyplot as plt
    from matplotlib.colors import ListedColormap, BoundaryNorm
    from matplotlib.patches import Patch


    # --------------------------------------------------------------------------
    # Lettura immagine ENVI (results.cluster, results.group.X, ecc.)
    # --------------------------------------------------------------------------

    def find_hdr(cluster_path: Path) -> Path:
    """Cerca l'header ENVI associato al file dati."""
    candidates = [
    cluster_path.with_suffix(cluster_path.suffix + ".hdr"),
    cluster_path.with_suffix(".hdr"),
    Path(str(cluster_path) + ".hdr"),
    ]
    for c in candidates:
    if c.exists():
    return c
    raise FileNotFoundError(
    f"Nessun file .hdr trovato per {cluster_path}. "
    f"Passalo esplicitamente con --hdr."
    )


    def parse_envi_hdr(hdr_path: Path) -> dict:
    """Parser minimale di un header ENVI (testo chiave=valore)."""
    text = hdr_path.read_text(errors="ignore")
    info = {}
    for key in ("samples", "lines", "bands", "data type",
    "interleave", "byte order", "header offset"):
    m = re.search(rf"{key}\s*=\s*(\S+)", text, re.IGNORECASE)
    if m:
    info[key.replace(" ", "_")] = m.group(1)
    required = ("samples", "lines", "data_type")
    missing = [k for k in required if k not in info]
    if missing:
    raise ValueError(f"Header ENVI incompleto, mancano: {missing}")
    return info


    ENVI_DTYPES = {
    "1": np.uint8, "2": np.int16, "3": np.int32, "4": np.float32,
    "5": np.float64, "6": np.complex64, "9": np.complex128,
    "12": np.uint16, "13": np.uint32, "14": np.int64, "15": np.uint64,
    }


    def read_envi_raw(data_path: Path, hdr: dict) -> np.ndarray:
    """Legge un raster ENVI senza dipendenze esterne (fallback)."""
    samples = int(hdr["samples"])
    lines = int(hdr["lines"])
    bands = int(hdr.get("bands", 1))
    dtype = ENVI_DTYPES.get(hdr.get("data_type", "2"), np.int16)
    offset = int(hdr.get("header_offset", 0))
    byte_order = hdr.get("byte_order", "0")
    interleave = hdr.get("interleave", "bsq").lower()

    arr = np.fromfile(data_path, dtype=dtype, offset=offset)
    expected = samples * lines * bands
    arr = arr[:expected]
    if byte_order == "1": # big-endian
    arr = arr.byteswap().newbyteorder()

    if interleave == "bsq":
    arr = arr.reshape(bands, lines, samples)
    elif interleave == "bil":
    arr = arr.reshape(lines, bands, samples).transpose(1, 0, 2)
    elif interleave == "bip":
    arr = arr.reshape(lines, samples, bands).transpose(2, 0, 1)
    else:
    raise ValueError(f"Interleave ENVI non riconosciuto: {interleave}")

    return arr[0] if bands == 1 else arr


    def load_cluster_map(cluster_path: Path, hdr_path: Path | None) -> np.ndarray:
    """Carica la mappa raster, usando rasterio se disponibile."""
    try:
    import rasterio
    with rasterio.open(cluster_path) as src:
    return src.read(1)
    except Exception:
    pass # fallback manuale

    if hdr_path is None:
    hdr_path = find_hdr(cluster_path)
    hdr = parse_envi_hdr(hdr_path)
    band = read_envi_raw(cluster_path, hdr)
    if band.ndim == 3:
    band = band[0]
    return band


    # --------------------------------------------------------------------------
    # Metadati di classificazione incorporati nell'header ENVI
    # (class names / class lookup) - spesso è qui, non in un color.keys esterno
    # --------------------------------------------------------------------------

    def parse_envi_classification(hdr_path: Path) -> dict | None:
    """
    Cerca nel .hdr i campi ENVI standard:
    class names = {Unclassified, Kaolinite, Goethite, ...}
    class lookup = {0,0,0, 255,0,0, 200,120,0, ...}
    e ritorna {id: (r,g,b,nome)} se presenti, altrimenti None.
    """
    text = hdr_path.read_text(errors="ignore")

    def extract_braced(field):
    m = re.search(rf"{field}\s*=\s*\{{([^}}]*)\}}", text,
    re.IGNORECASE | re.DOTALL)
    return m.group(1) if m else None

    names_raw = extract_braced("class names")
    lookup_raw = extract_braced("class lookup")
    if not names_raw or not lookup_raw:
    return None

    names = [n.strip() for n in names_raw.split(",") if n.strip()]
    nums = [int(x) for x in re.findall(r"-?\d+", lookup_raw)]
    if len(nums) < len(names) * 3:
    return None

    result = {}
    for i, name in enumerate(names):
    r, g, b = nums[i * 3], nums[i * 3 + 1], nums[i * 3 + 2]
    result[i] = (r, g, b, name)
    return result


    # --------------------------------------------------------------------------
    # Parsing color.keys (file di testo esterno, se lo trovi separatamente)
    # --------------------------------------------------------------------------

    def parse_color_keys(path: Path, debug: bool = False) -> dict:
    """
    Ritorna {id_materiale: (r, g, b, nome)}.

    Prova più pattern, perché il formato varia tra run Tetracorder:
    1) " 12 255 128 0 Kaolinite_CM9.spc"
    2) "12 Kaolinite_CM9 R=255 G=128 B=0"
    3. CSV-like: "12,255,128,0,Kaolinite"
    """
    patterns = [
    re.compile(r"^\s*(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\S.*)$"),
    re.compile(r"^\s*(\d+)\s+(\S+).*?R\s*=?\s*(\d+).*?G\s*=?\s*(\d+).*?B\s*=?\s*(\d+)", re.IGNORECASE),
    re.compile(r"^\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*(\S.*)$"),
    ]

    result = {}
    unmatched = []
    for raw_line in path.read_text(errors="ignore").splitlines():
    line = raw_line.strip()
    if not line or line.startswith("#") or line.startswith(";"):
    continue

    m = patterns[0].match(line)
    if m:
    idx, r, g, b, name = m.groups()
    result[int(idx)] = (int(r), int(g), int(b), name.strip())
    continue

    m = patterns[1].match(line)
    if m:
    idx, name, r, g, b = m.groups()
    result[int(idx)] = (int(r), int(g), int(b), name.strip())
    continue

    m = patterns[2].match(line)
    if m:
    idx, r, g, b, name = m.groups()
    result[int(idx)] = (int(r), int(g), int(b), name.strip())
    continue

    unmatched.append(raw_line)

    if debug:
    print(f"[debug] {len(result)} voci interpretate, "
    f"{len(unmatched)} righe non riconosciute.")
    for u in unmatched[:15]:
    print(" ??", u)

    if not result:
    raise ValueError(
    "Nessuna riga di color.keys è stata interpretata. "
    "Rilancia con --debug-colorkeys e mandami le prime righe del file "
    "cosi' adatto il parser al formato esatto."
    )
    return result


    # --------------------------------------------------------------------------
    # Plot
    # --------------------------------------------------------------------------

    def plot_classification(class_map: np.ndarray, color_keys: dict,
    output: Path, title: str, max_legend_items: int = 40):
    present_ids = sorted(i for i in np.unique(class_map) if i != 0 and i in color_keys)

    if not present_ids:
    print("Attenzione: nessun ID presente nella mappa ha una corrispondenza "
    "in color.keys. Controlla che i due file provengano dallo stesso run.")

    # id 0 = nessun match -> sfondo grigio chiaro
    ids = [0] + present_ids
    colors = [(0.92, 0.92, 0.92)]
    labels = ["(nessun match)"]
    for i in present_ids:
    r, g, b, name = color_keys[i]
    colors.append((r / 255, g / 255, b / 255))
    labels.append(f"{i}: {name}")

    cmap = ListedColormap(colors)
    bounds = ids + [ids[-1] + 1]
    norm = BoundaryNorm(bounds, cmap.N)

    remap = np.zeros_like(class_map, dtype=np.int32)
    for new_i, orig_i in enumerate(ids):
    remap[class_map == orig_i] = new_i

    fig, ax = plt.subplots(figsize=(10, 8))
    ax.imshow(remap, cmap=ListedColormap(colors), interpolation="nearest")
    ax.set_title(title)
    ax.set_xticks([])
    ax.set_yticks([])

    legend_ids = present_ids[:max_legend_items]
    handles = [Patch(facecolor=(color_keys[i][0] / 255,
    color_keys[i][1] / 255,
    color_keys[i][2] / 255),
    label=f"{color_keys[i][3]}")
    for i in legend_ids]
    if len(present_ids) > max_legend_items:
    handles.append(Patch(facecolor="white",
    label=f"... +{len(present_ids) - max_legend_items} altri"))

    ax.legend(handles=handles, bbox_to_anchor=(1.02, 1), loc="upper left",
    fontsize=7, frameon=False, ncol=1)

    fig.tight_layout()
    fig.savefig(output, dpi=200, bbox_inches="tight")
    print(f"Salvato: {output} ({len(present_ids)} materiali distinti trovati)")


    # --------------------------------------------------------------------------
    # Main
    # --------------------------------------------------------------------------

    def main():
    p = argparse.ArgumentParser(description=__doc__,
    formatter_class=argparse.RawDescriptionHelpFormatter)
    p.add_argument("--cluster", required=True, type=Path,
    help="File risultati (es. results.cluster.sorted)")
    p.add_argument("--hdr", type=Path, default=None,
    help="Header ENVI (.hdr), se non individuabile automaticamente")
    p.add_argument("--colorkeys", required=False, type=Path, default=None,
    help="File color.keys testuale (opzionale: se assente, "
    "lo script cerca 'class names'/'class lookup' "
    "direttamente nell'header ENVI del raster)")
    p.add_argument("--output", type=Path, default=Path("tetracorder_map.png"),
    help="File immagine di output (default: tetracorder_map.png)")
    p.add_argument("--title", default="Classificazione Tetracorder")
    p.add_argument("--debug-colorkeys", action="store_true",
    help="Stampa il dettaglio del parsing di color.keys")
    args = p.parse_args()

    class_map = load_cluster_map(args.cluster, args.hdr)

    color_keys = None
    hdr_path = args.hdr or find_hdr(args.cluster)
    if hdr_path.exists():
    color_keys = parse_envi_classification(hdr_path)
    if color_keys:
    print(f"Trovate {len(color_keys)} classi nei metadati ENVI "
    f"dell'header ({hdr_path.name}).")

    if color_keys is None:
    if args.colorkeys is None:
    print("Nessuna classificazione trovata nell'header ENVI e "
    "nessun --colorkeys fornito. Passa un file color.keys "
    "testuale con --colorkeys, oppure mandami un estratto "
    "del .hdr per capire dove sta l'informazione colore/nome.",
    file=sys.stderr)
    sys.exit(1)
    color_keys = parse_color_keys(args.colorkeys, debug=args.debug_colorkeys)

    plot_classification(class_map, color_keys, args.output, args.title)


    if __name__ == "__main__":
    sys.exit(main())


     

     

     

    Entropia in telerilevamento

    Questa e' un risorsa a cui aggrapparsi quando non c'e' niente nell'informazione spettrale che possa aiutare In pratica si us...