mercoledì 7 ottobre 2026

CEDev e CEmu TI-84 Plus CE

Programmazione di TI-84 Plus CE (identica alla TI-83 Premium CE, versione francese) con un emulatore 

 

Per prima cosa si inizia con il compilare l'emulatore

sudo apt update sudo apt install liblua5.4-dev libpng-dev 


git clone https://github.com/ThePhD/sol2.git
cd sol2
mkdir build && cd build
cmake .. -Dsol2_BUILD_TESTS=OFF -Dsol2_BUILD_EXAMPLES=OFF
sudo cmake --install . 

git clone --recursive https://github.com/CE-Programming/CEmu.git 

cd CEmu

cmake ../gui/qt

cmake -DLUA_VERSION=5.4 ../gui/qt 


mkdir build && cd build
cmake ..

Per il funzionamento dell'emulatore si deve scaricare il firmware in formato .rom. Il link seguente e' per il firmware 5.3 

https://web.archive.org/web/20231217022137/https://tiroms.weebly.com/uploads/1/1/0/5/110560031/ti-84ce.rom

successivamente si puo' aggiornare il firmware alla versione 5.8.3  da

https://tiplanet.org/forum/archives_voir.php?id=4601907 

ed inviandolo all'emulatore tramite 

~/CEmu/build/CEmu --send /home/luca/Downloads/os_prd_48.8eu 

messo a punto l'emulatore si passa al compilatore

sudo mkdir -p /opt/CEdev
cd /opt/CEdev
curl -sL https://github.com/CE-Programming/toolchain/releases/latest/download/CEdev-Linux.tar.gz | sudo tar -xz 

echo 'export CEDEV=/opt/CEdev' >> ~/.bashrc
echo 'export PATH=$CEDEV/bin:$PATH' >> ~/.bashrc
source ~/.bashrc
 

 

in /opt/CEDev/examples ci sono numerosi esempi che si compilano tramite make come hello world. Dopo la compilazione si trova il file /bin/DEMO.8xp che si puo' inviare all'emulatore tramite

 ~/CEmu/build/CEmu --send ./bin/DEMO.8xp 

 

per lanciare il programma si preme il tasto prgm (quarta fila dall'alto, terzo da sinistra), si ha la lista dei programmi installati, si sceglie il numero relativo al programma (si avra' prgmDEMO) e si preme enter  

se si ha l'errore libload si deve inviare il file clibs.8xg scaricato da qui

https://github.com/CE-Programming/libraries/releases/tag/v15.0 

tramite 

~/CEmu/build/CEmu --send ~/Downloads/clibs.8xg

di seguito un esempio di Mandelbrot scritto 

/*
* Mandelbrot per TI-84 Plus CE - CE C Toolchain (CEdev)
*
* Usa solo gli header del toolchain (ti/getcsc.h, ti/screen.h): nessuna
* libreria esterna, quindi sulla calcolatrice non serve LibLoad.
*
* Virgola fissa con 8 bit frazionari (1.0 = 256). Sull'ez80 `int` e' a
* 24 bit: con |valore| < 1700 il prodotto resta sotto 2^23, quindi non
* c'e' overflow nemmeno nell'iterazione in cui il punto diverge.
*
* Ottimizzazioni (le stesse di mandelf.asm):
* - test algebrico per cardioide e bulbo di periodo 2 (niente iterazioni);
* - simmetria rispetto all'asse reale: si calcolano le righe 0..120 e le
* righe 1..119 vengono copiate in 239..121.
*/

#include <stdint.h>
#include <string.h>
#include <ti/getcsc.h>
#include <ti/screen.h>

#define LCD_WIDTH 320
#define LCD_HEIGHT 240

#define F 8 /* bit frazionari */
#define ONE (1 << F)
#define STEP 3 /* passo per pixel (3/256) */
#define MAXITER 30
#define X0 (-640) /* c_re iniziale: -2.5 */
#define Y0 (-360) /* c_im iniziale: -1.406 */

#define VRAM ((uint16_t *)0xD40000)
#define LCD_UPBASE (*(volatile uint24_t *)0xE30010)
#define LCD_CTRL (*(volatile uint8_t *)0xE30018)

static uint16_t palette[MAXITER + 1];

static uint16_t rgb565(int r, int g, int b)
{
return (uint16_t)(((r >> 3) << 11) | ((g >> 2) << 5) | (b >> 3));
}

/* 1 se c sta nel bulbo di periodo 2 o nella cardioide principale */
static int in_main_bulbs(int cx, int cy2)
{
int t = cx + ONE;
int xm, q;

if (((t * t) >> F) + cy2 < ONE / 16) /* (x+1)^2 + y^2 < 1/16 */
return 1;

xm = cx - ONE / 4;
q = ((xm * xm) >> F) + cy2; /* (x-1/4)^2 + y^2 */
return ((q * (q + xm)) >> F) < (cy2 >> 2); /* q(q+x-1/4) < y^2/4 */
}

int main(void)
{
uint16_t *vram = VRAM;
int n, px, py;

os_ClrHomeFull();

/* LCD: buffer in vRam, 16 bpp (5:6:5) */
LCD_UPBASE = 0xD40000;
LCD_CTRL = 0x2D;

/* colori: blu per i punti che divergono subito, arancio vicino al bordo */
palette[0] = 0;
for (n = 1; n < MAXITER; n++) {
int t = n * 255 / MAXITER;
palette[n] = rgb565(t, (t * t) >> 8, 255 - t);
}
palette[MAXITER] = 0; /* interno: nero */

for (py = 0; py <= LCD_HEIGHT / 2; py++) {
int cy = Y0 + py * STEP;
int cy2 = (cy * cy) >> F;
int near_axis = cy2 < 112; /* |y| < ~0.66 */
int cx = X0;
uint16_t *row = vram + py * LCD_WIDTH;

for (px = 0; px < LCD_WIDTH; px++, cx += STEP) {
if (near_axis && cx < ONE * 3 / 8 && cx >= -(ONE * 5 / 4)
&& in_main_bulbs(cx, cy2)) {
n = MAXITER;
} else {
int x = 0, y = 0, x2 = 0, y2 = 0;

for (n = 0; n < MAXITER && x2 + y2 < 4 * ONE; n++) {
y = ((x * y) >> (F - 1)) + cy; /* 2xy + cy */
x = x2 - y2 + cx;
x2 = (x * x) >> F;
y2 = (y * y) >> F;
}
}
row[px] = palette[n];
}

/* specchio: riga py -> riga 240-py (per py = 1..119) */
if (py > 0 && py < LCD_HEIGHT / 2) {
memcpy(vram + (LCD_HEIGHT - py) * LCD_WIDTH, row,
LCD_WIDTH * sizeof(uint16_t));
}
}

while (os_GetCSC()) { } /* aspetta il rilascio dei tasti */
while (!os_GetCSC()) { } /* poi un nuovo tasto */

os_ClrHomeFull();
os_DrawStatusBar();
return 0;
}


  

# ----------------------------
# Makefile per CE C Toolchain
# ----------------------------

NAME = MANDELC
DESCRIPTION = "Mandelbrot"
COMPRESSED = NO

CFLAGS = -Wall -Wextra -O3
CXXFLAGS = -Wall -Wextra -O3

# ----------------------------

include $(CEDEV)/meta/makefile.mk


Sulla calcolatrice si possono esegire anche programmi in Assembler ma c'e' un dettaglio importante. Texas Instruments da un firmware in poi ha rimosso questa opzione e questo succede anche l'emulare...ho quindi dovuto fare il downgrade alla versione 5.3 per effettuare questa prova (tecnicamente non e' vero..esiste un jailbreak per continuare ad usare programmi compilati in assembler su firmware moderni)

 per compilare si usa fasmg programma.asm MANDEL.8xp

; Mandelbrot per TI-84 Plus CE (CEdev / fasmg)
; Schermo 320x240 a 16 bpp (modalita' predefinita dell'OS), scrittura diretta in vRam.
;
; Aritmetica in virgola fissa con segno: 3 bit interi + 9 bit frazionari (1.0 = 512).
; I valori sono tenuti come interi a 24 bit con segno esteso.
; c_re : da -2.25 a +0.875 (passo 5/512, 320 colonne)
; c_im : da -1.17 a +1.17 (passo 5/512, 240 righe)
; Colore = iterazioni * 0x0841 (sfumatura di grigio); interno dell'insieme = nero.

include 'include/ez80.inc'
include 'include/ti84pceg.inc'
include 'include/tiformat.inc'

format ti executable 'MANDEL'

MAXITER := 30

call ti.ClrScrnFull

; LCD: buffer in vRam, 16 bpp (5:6:5)
ld hl, ti.vRam
ld (0E30010h), hl ; mpLcdUpbase
ld a, 02Dh
ld (0E30018h), a ; mpLcdCtrl

; --- TEST: riempie lo schermo di grigio-viola. Se NON lo vedi cambiare
; --- subito, le scritture in vRam non arrivano allo schermo. ---
ld hl, ti.vRam
ld de, ti.vRam+1
ld bc, 320*240*2-1
ld (hl), 0A5h
ldir

ld hl, ti.vRam
ld (pixptr), hl
ld hl, -600 ; cy iniziale (-1.17)
ld (cy), hl

row_loop:
ld hl, -1152 ; cx iniziale (-2.25)
ld (cx), hl

col_loop:
ld hl, 0
ld (zx), hl
ld (zy), hl
xor a, a
ld (iter), a

iter_loop:
ld hl, (zx)
ld de, (zx)
call fmul
ld (zx2), hl ; x^2
ld hl, (zy)
ld de, (zy)
call fmul
ld (zy2), hl ; y^2

ld de, (zx2)
add hl, de ; x^2 + y^2
ld de, 4*512
or a, a
sbc hl, de
jp nc, escaped ; >= 4.0 -> divergente

ld hl, (zx)
ld de, (zy)
call fmul ; x*y
add hl, hl ; 2xy
ld de, (cy)
add hl, de
ld (zy), hl ; y = 2xy + cy

ld hl, (zx2)
ld de, (zy2)
or a, a
sbc hl, de ; x^2 - y^2
ld de, (cx)
add hl, de
ld (zx), hl ; x = x^2 - y^2 + cx

ld a, (iter)
inc a
ld (iter), a
cp a, MAXITER
jp nz, iter_loop

ld hl, 0 ; non e' scappato: nero
jr put_pixel

escaped:
ld a, (iter)
ld hl, 0
ld l, a
ld h, 65
mlt hl ; n*65 = n + (n << 6)
ld de, 0
ld d, a
ld e, 8
mlt de ; n*8
ld d, e
ld e, 0 ; n << 11
add hl, de ; colore RGB565 = n*0x0841

put_pixel:
ex de, hl
ld hl, (pixptr)
ld (hl), e
inc hl
ld (hl), d
inc hl
ld (pixptr), hl

ld hl, (cx)
ld de, 5
add hl, de
ld (cx), hl
ld de, 448 ; -1152 + 320*5
or a, a
sbc hl, de
jp nz, col_loop

ld hl, (cy)
ld de, 5
add hl, de
ld (cy), hl
ld de, 600 ; -600 + 240*5
or a, a
sbc hl, de
jp nz, row_loop

wait_release: ; aspetta che tutti i tasti siano rilasciati
call ti.GetCSC
or a, a
jr nz, wait_release
wait_press: ; poi aspetta un nuovo tasto
call ti.GetCSC
or a, a
jr z, wait_press
call ti.ClrScrnFull
call ti.HomeUp
call ti.DrawStatusBar
ret

; ---------------------------------------------------------------
; HL = (HL * DE) >> 9 con segno, |HL|,|DE| < 4096
; Distrugge AF, BC, DE, HL, IX
; ---------------------------------------------------------------
fmul:
ld a, h
xor a, d ; bit 7 = segno del risultato
push af
bit 7, h
call nz, negHL
ex de, hl
bit 7, h
call nz, negHL
ex de, hl ; ora HL=H:L e DE=D:E sono positivi

ld ix, 0
ld bc, 0
ld b, l
ld c, e
mlt bc ; L*E
ld c, b
ld b, 0 ; (L*E) >> 8
add ix, bc
ld bc, 0
ld b, h
ld c, e
mlt bc ; H*E
add ix, bc
ld bc, 0
ld b, l
ld c, d
mlt bc ; L*D
add ix, bc
ld bc, 0
ld b, h
ld c, d
mlt bc ; H*D (< 256)
ld b, c
ld c, 0 ; (H*D) << 8
add ix, bc ; IX = prodotto >> 8
push ix
pop hl
srl h
rr l ; HL = prodotto >> 9
pop af
bit 7, a
call nz, negHL
ret

negHL: ; HL = -HL, DE preservato
push de
ex de, hl
or a, a
sbc hl, hl
sbc hl, de
pop de
ret

; ---------------------------------------------------------------
; Variabili (24 bit)
; ---------------------------------------------------------------
pixptr: dl 0
cx: dl 0
cy: dl 0
zx: dl 0
zy: dl 0
zx2: dl 0
zy2: dl 0
iter: db 0


https://www.ticalc.org/pub/84plusce/asm/ 

Se si crea una versione in TiBasic la velocita' di esecuzione crolla miseramente

Per caricare il softare da PC sull'emulatore si deve usare un editor compatibile come https://www.cemetech.net/sc/ esportare il file e inviarlo all'emulatore (il formato non e' un semplice ascii)  salvando con nome tutto maiuscolo ed estensione 8xp

/////////////////////////////////////////////////////////////////////////////////////////

AxesOff
ClrDraw
24→N
1→S
For(R,0,82,S)
(82-R)*.0125→B
B²→D
For(C,0,264,S)
C*.0125-2.2→A
(A-.25)²+D→Q
If Q*(Q+A-.25)<.25D or (A+1)²+D<.0625
Then
N→K
Else
0→X
0→Y
0→K
While K<N and X²+Y²<4
X²-Y²+A→T
2XY+B→Y
T→X
K+1→K
End
End
If K>2
Then
20+(K>2)+(K>6)+(K>12)+(K>19)→E
If K=N
12→E
Pxl-On(R,C,E)
Pxl-On(164-R,C,E)
End
End
End
Repeat getKey
End
 

 

 




martedì 6 ottobre 2026

Cronaca di un fallimento: crisotilo su Enmap (3)

Per cercare di migliorare le librerie dell'amianto su Enmap ho provato ad essere piu' selettivo

Usando WorldCover Esa Map ho selezionato in QGis i build-up che sono indicati da Banda 1 Tavolozza 50

 

 ho tagliato il raster usando come base l'impronta dell'immagine Enmap con Processing → Toolbox → GDAL → Raster extraction → Clip raster by extent

 

 

 alla fine ho ottenuto un raster 0-1 in cui 1 indica la posizione dei built-up e 0 tutte le altre classiche

 

A questo punto ho convertito il raster in un tema puntuale tramite Processing → Toolbox → GDAL → Conversione raster → Pixel raster in punti

Alla fine ho un tema puntuale di tetti..a questo punto devo selezionare solo i tetti che non sono di amianto

Per questo motivo ho creato un buffer di 60 metri attorno ai punti del tema con i punti dei tetti di amianto_PRA_pubblico con Processing → Toolbox → Geometria vettoriale → Buffer

 

siamo quasi alla fine, togliendo dal tema tutti i pixel che sono esterni al buffer si puo' usare Processing → Toolbox → Estrai per posizione

Per estrarre dei punti casuali all'interno del tema ho usato Processing Toolobox Estrazione del vettore Estrazione casuale estraendo un nuovo tema di 20000 punti 

In seguito Enmap Toolbox Spectral Library Extract spectral profiles from raster layer per avere una libreria spettrale di tetti non in amianto

Come nel precedente post ho creato un campo boolean amianto (true per i tetti in amianto, false l'ovvio opposto) ed ho unito le due librerie con 

Per unire la libreria dei tetti in amianto e quelle non in amianto si apre la libreria A, si selezionano tutti i profili si copiano in Spectral Library Viewer di Enmap Toolbox e si incollano nella libreria B

Per rendere il modello misurabile la libreria e' stata splittata in 70% train, 20% test e 10% validate 

import json, pickle
import numpy as np
import pandas as pd
import geopandas as gpd
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import StratifiedGroupKFold
from sklearn.metrics import (average_precision_score, roc_auc_score,
                             precision_recall_curve, precision_score,
                             recall_score, f1_score)

# ---------------- parametri ----------------
GPKG = "C:/Users/l.innocenti/Documents/MF/worldview/libreria/unione.gpkg"
LAYER = "no_amianto2"
FIELD_PROFILES = "profile"
FIELD_BG = "amianto"              # 1.0 = amianto; NaN/vuoto = background
CELL = 500                        # metri
EDGE_DROP = [(0, 550), (2400, 3000)]                 # sempre escluse
WATER_DROP = [(1300, 1500), (1750, 2150)]            # escluse solo nelle varianti "senza bande"
N_TREES = 500
SEEDS = [0, 1, 2]                 # ogni seed cambia sia lo split spaziale sia il random forest
N_FOLDS = 10
FOLDS_TRAIN, FOLDS_TEST, FOLDS_VAL = (0, 1, 2, 3, 4, 5, 6), (7, 8), (9,)
OUT_CSV = "C:/Users/l.innocenti/Documents/MF/ablazione_risultati.csv"
TRUE_VALUES = {"true", "1", "1.0", "yes", "si", "sì", "t"}
# --------------------------------------------


def parse_profile(v):
    if v is None:
        return None
    if isinstance(v, dict):
        return v
    if isinstance(v, (bytes, bytearray, memoryview)):
        b = bytes(v)
        try:
            return json.loads(b.decode("utf-8"))
        except Exception:
            return pickle.loads(b)           # solo su file tuoi, di cui ti fidi
    if isinstance(v, str):
        return json.loads(v)
    return None


def normalize_spectra(A, mode):
    if mode is None:
        return A
    n = np.linalg.norm(A, axis=1, keepdims=True)
    return A / np.clip(n, 1e-12, None)


def make_rf(seed):
    return RandomForestClassifier(
        n_estimators=N_TREES, max_features="sqrt", min_samples_leaf=3,
        class_weight="balanced_subsample", n_jobs=-1, random_state=seed)


# --- lettura ---
df = gpd.read_file(GPKG, layer=LAYER)
print("Record letti:", len(df), "| campi:", list(df.columns))
print(df[FIELD_BG].value_counts(dropna=False))

prof = [parse_profile(v) for v in df[FIELD_PROFILES]]
lengths = [len(p["y"]) if p and p.get("y") is not None else 0 for p in prof]
n_bands = max(set(lengths), key=lengths.count)
ok = np.array([l == n_bands for l in lengths])
first = next(p for p, o in zip(prof, ok) if o)
wl = np.array(first["x"], float)
bbl = np.array(first["bbl"], bool) if first.get("bbl") is not None else np.ones(n_bands, bool)

# maschera larga (con bande reintrodotte) e stretta (senza)
mask_wide = bbl.copy()
for lo, hi in EDGE_DROP:
    mask_wide &= ~((wl >= lo) & (wl <= hi))
mask_narrow = mask_wide.copy()
for lo, hi in WATER_DROP:
    mask_narrow &= ~((wl >= lo) & (wl <= hi))
print(f"Bande: larga {mask_wide.sum()} | stretta {mask_narrow.sum()}")

X = np.array([np.asarray(p["y"], float) for p, o in zip(prof, ok) if o])
keep = np.where(ok)[0]
lab = df[FIELD_BG].iloc[keep]
y = lab.map(lambda v: 1 if str(v).strip().lower() in TRUE_VALUES else 0).to_numpy().astype(int)

# stessi campioni per tutte le varianti: filtro sulla maschera larga
Xw = X[:, mask_wide]
good = np.isfinite(Xw).all(axis=1) & (np.abs(Xw).sum(axis=1) > 0)
X, y, keep, Xw = X[good], y[good], keep[good], Xw[good]
assert len(Xw) == len(y) == len(keep), "Spettri e etichette non allineati"
print(f"Campioni: {len(y)} | amianto: {y.sum()} | background: {(y == 0).sum()}")
if y.sum() == 0 or (y == 0).sum() == 0:
    raise SystemExit("Una sola classe presente: controlla FIELD_BG")

# indici delle colonne della maschera stretta dentro la larga
sub_narrow = mask_narrow[mask_wide]

# --- gruppi spaziali ---
g = df.iloc[keep]
if g.crs is not None and g.crs.is_geographic:
    g = g.to_crs(32632)
cx = (g.geometry.x.to_numpy() // CELL).astype(np.int64)
cy = (g.geometry.y.to_numpy() // CELL).astype(np.int64)
groups = -(cx * 100000 + cy) - 1
print("Celle spaziali:", len(np.unique(groups)))

configs = [("grezzo", None, False), ("grezzo", None, True),
           ("L2", "l2", False), ("L2", "l2", True)]

rows = []
for seed in SEEDS:
    sgkf = StratifiedGroupKFold(n_splits=N_FOLDS, shuffle=True, random_state=seed)
    fold_id = np.zeros(len(y), int)
    for k, (_, te) in enumerate(sgkf.split(X, y, groups)):
        fold_id[te] = k
    tr = np.where(np.isin(fold_id, FOLDS_TRAIN))[0]
    te = np.where(np.isin(fold_id, FOLDS_TEST))[0]
    va = np.where(np.isin(fold_id, FOLDS_VAL))[0]

    for norm_name, norm, wide in configs:
        Xc = Xw if wide else Xw[:, sub_narrow]
        Xc = normalize_spectra(Xc, norm)
        rf = make_rf(seed).fit(Xc[tr], y[tr])
        pv = rf.predict_proba(Xc[va])[:, 1]
        pt = rf.predict_proba(Xc[te])[:, 1]

        prec, rec, thr = precision_recall_curve(y[va], pv)
        f1c = 2 * prec[:-1] * rec[:-1] / np.clip(prec[:-1] + rec[:-1], 1e-9, None)
        t = float(thr[f1c.argmax()])
        yp = (pt >= t).astype(int)

        rows.append({
            "seed": seed, "spettri": norm_name,
            "bande_1300-1500/1750-2150": "incluse" if wide else "escluse",
            "n_bande": Xc.shape[1],
            "PR-AUC": average_precision_score(y[te], pt),
            "ROC-AUC": roc_auc_score(y[te], pt),
            "soglia": t,
            "precision": precision_score(y[te], yp, zero_division=0),
            "recall": recall_score(y[te], yp),
            "F1": f1_score(y[te], yp),
        })
        r = rows[-1]
        print(f"seed {seed} | {norm_name:6s} | bande {r['bande_1300-1500/1750-2150']:7s} "
              f"| PR-AUC {r['PR-AUC']:.3f} | ROC-AUC {r['ROC-AUC']:.3f} | F1 {r['F1']:.3f}")

res = pd.DataFrame(rows)
res.to_csv(OUT_CSV, index=False)

keys = ["spettri", "bande_1300-1500/1750-2150", "n_bande"]
metrics = ["PR-AUC", "ROC-AUC", "precision", "recall", "F1"]
summary = res.groupby(keys)[metrics].agg(["mean", "std"]).round(3)
pd.set_option("display.width", 250)
pd.set_option("display.max_columns", 50)
print(f"\n=== RISULTATI SUL TEST (media e dev. std su {len(SEEDS)} seed, CELL={CELL} m) ===")
print(summary)
print(f"\nBaseline PR-AUC (prevalenza amianto): {y.mean():.3f}")
print("Risultati per singolo seed salvati in", OUT_CSV)

 

import json, pickle
import numpy as np
import pandas as pd
import geopandas as gpd
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import StratifiedGroupKFold
from sklearn.metrics import (average_precision_score, roc_auc_score,
                             precision_recall_curve, precision_score,
                             recall_score, f1_score)

# ---------------- parametri ----------------
GPKG = "C:/Users/l.innocenti/Documents/MF/worldview/libreria/unione.gpkg"
LAYER = "no_amianto2"
FIELD_PROFILES = "profile"
FIELD_BG = "amianto"              # 1.0 = amianto; NaN/vuoto = background
CELL = 500                        # metri
EDGE_DROP = [(0, 550), (2400, 3000)]                 # sempre escluse
WATER_DROP = [(1300, 1500), (1750, 2150)]            # escluse solo nelle varianti "senza bande"
N_TREES = 500
SEEDS = [0, 1, 2]                 # ogni seed cambia sia lo split spaziale sia il random forest
N_FOLDS = 10
FOLDS_TRAIN, FOLDS_TEST, FOLDS_VAL = (0, 1, 2, 3, 4, 5, 6), (7, 8), (9,)
OUT_CSV = "C:/Users/l.innocenti/Documents/MF/ablazione_risultati.csv"
TRUE_VALUES = {"true", "1", "1.0", "yes", "si", "sì", "t"}
# --------------------------------------------


def parse_profile(v):
    if v is None:
        return None
    if isinstance(v, dict):
        return v
    if isinstance(v, (bytes, bytearray, memoryview)):
        b = bytes(v)
        try:
            return json.loads(b.decode("utf-8"))
        except Exception:
            return pickle.loads(b)           # solo su file tuoi, di cui ti fidi
    if isinstance(v, str):
        return json.loads(v)
    return None


def normalize_spectra(A, mode):
    if mode is None:
        return A
    n = np.linalg.norm(A, axis=1, keepdims=True)
    return A / np.clip(n, 1e-12, None)


def make_rf(seed):
    return RandomForestClassifier(
        n_estimators=N_TREES, max_features="sqrt", min_samples_leaf=3,
        class_weight="balanced_subsample", n_jobs=-1, random_state=seed)


# --- lettura ---
df = gpd.read_file(GPKG, layer=LAYER)
print("Record letti:", len(df), "| campi:", list(df.columns))
print(df[FIELD_BG].value_counts(dropna=False))

prof = [parse_profile(v) for v in df[FIELD_PROFILES]]
lengths = [len(p["y"]) if p and p.get("y") is not None else 0 for p in prof]
n_bands = max(set(lengths), key=lengths.count)
ok = np.array([l == n_bands for l in lengths])
first = next(p for p, o in zip(prof, ok) if o)
wl = np.array(first["x"], float)
bbl = np.array(first["bbl"], bool) if first.get("bbl") is not None else np.ones(n_bands, bool)

# maschera larga (con bande reintrodotte) e stretta (senza)
mask_wide = bbl.copy()
for lo, hi in EDGE_DROP:
    mask_wide &= ~((wl >= lo) & (wl <= hi))
mask_narrow = mask_wide.copy()
for lo, hi in WATER_DROP:
    mask_narrow &= ~((wl >= lo) & (wl <= hi))
print(f"Bande: larga {mask_wide.sum()} | stretta {mask_narrow.sum()}")

X = np.array([np.asarray(p["y"], float) for p, o in zip(prof, ok) if o])
keep = np.where(ok)[0]
lab = df[FIELD_BG].iloc[keep]
y = lab.map(lambda v: 1 if str(v).strip().lower() in TRUE_VALUES else 0).to_numpy().astype(int)

# stessi campioni per tutte le varianti: filtro sulla maschera larga
Xw = X[:, mask_wide]
good = np.isfinite(Xw).all(axis=1) & (np.abs(Xw).sum(axis=1) > 0)
X, y, keep, Xw = X[good], y[good], keep[good], Xw[good]
assert len(Xw) == len(y) == len(keep), "Spettri e etichette non allineati"
print(f"Campioni: {len(y)} | amianto: {y.sum()} | background: {(y == 0).sum()}")
if y.sum() == 0 or (y == 0).sum() == 0:
    raise SystemExit("Una sola classe presente: controlla FIELD_BG")

# indici delle colonne della maschera stretta dentro la larga
sub_narrow = mask_narrow[mask_wide]

# --- gruppi spaziali ---
g = df.iloc[keep]
if g.crs is not None and g.crs.is_geographic:
    g = g.to_crs(32632)
cx = (g.geometry.x.to_numpy() // CELL).astype(np.int64)
cy = (g.geometry.y.to_numpy() // CELL).astype(np.int64)
groups = -(cx * 100000 + cy) - 1
print("Celle spaziali:", len(np.unique(groups)))

configs = [("grezzo", None, False), ("grezzo", None, True),
           ("L2", "l2", False), ("L2", "l2", True)]

rows = []
for seed in SEEDS:
    sgkf = StratifiedGroupKFold(n_splits=N_FOLDS, shuffle=True, random_state=seed)
    fold_id = np.zeros(len(y), int)
    for k, (_, te) in enumerate(sgkf.split(X, y, groups)):
        fold_id[te] = k
    tr = np.where(np.isin(fold_id, FOLDS_TRAIN))[0]
    te = np.where(np.isin(fold_id, FOLDS_TEST))[0]
    va = np.where(np.isin(fold_id, FOLDS_VAL))[0]

    for norm_name, norm, wide in configs:
        Xc = Xw if wide else Xw[:, sub_narrow]
        Xc = normalize_spectra(Xc, norm)
        rf = make_rf(seed).fit(Xc[tr], y[tr])
        pv = rf.predict_proba(Xc[va])[:, 1]
        pt = rf.predict_proba(Xc[te])[:, 1]

        prec, rec, thr = precision_recall_curve(y[va], pv)
        f1c = 2 * prec[:-1] * rec[:-1] / np.clip(prec[:-1] + rec[:-1], 1e-9, None)
        t = float(thr[f1c.argmax()])
        yp = (pt >= t).astype(int)

        rows.append({
            "seed": seed, "spettri": norm_name,
            "bande_1300-1500/1750-2150": "incluse" if wide else "escluse",
            "n_bande": Xc.shape[1],
            "PR-AUC": average_precision_score(y[te], pt),
            "ROC-AUC": roc_auc_score(y[te], pt),
            "soglia": t,
            "precision": precision_score(y[te], yp, zero_division=0),
            "recall": recall_score(y[te], yp),
            "F1": f1_score(y[te], yp),
        })
        r = rows[-1]
        print(f"seed {seed} | {norm_name:6s} | bande {r['bande_1300-1500/1750-2150']:7s} "
              f"| PR-AUC {r['PR-AUC']:.3f} | ROC-AUC {r['ROC-AUC']:.3f} | F1 {r['F1']:.3f}")

res = pd.DataFrame(rows)
res.to_csv(OUT_CSV, index=False)

keys = ["spettri", "bande_1300-1500/1750-2150", "n_bande"]
metrics = ["PR-AUC", "ROC-AUC", "precision", "recall", "F1"]
summary = res.groupby(keys)[metrics].agg(["mean", "std"]).round(3)
pd.set_option("display.width", 250)
pd.set_option("display.max_columns", 50)
print(f"\n=== RISULTATI SUL TEST (media e dev. std su {len(SEEDS)} seed, CELL={CELL} m) ===")
print(summary)
print(f"\nBaseline PR-AUC (prevalenza amianto): {y.mean():.3f}")
print("Risultati per singolo seed salvati in", OUT_CSV)


 

In conclusione il modello ha trovato 642 tetti in amianto su 1014 (ne ha mancati 372) e ha segnalato per errore 806 pixel di background su 2542. 

Precision = TP / (TP + FP): 0.73 (tra i pixel che il modello segnala come amianto, quanti lo sono davvero) 

Recall = TP / (TP + FN): 0.057 (pixel che sono amianto, quanti il modello ne trova)

Bande più importanti:
banda 1449: 0.0112
banda 1128: 0.0099
banda 2225: 0.0089
banda 1140: 0.0085
banda 1996: 0.0083
banda 2216: 0.0081
banda 1152: 0.0079
banda 951: 0.0079
banda 941: 0.0079
banda 1259: 0.0078

 

CEDev e CEmu TI-84 Plus CE

Programmazione di TI-84 Plus CE (identica alla TI-83 Premium CE, versione francese) con un emulatore    Per prima cosa si inizia con il comp...