mercoledì 14 ottobre 2026

WebOdm e Aruco

Esiste gia' un plugin per usare aruco tag come marker all'interno di un progetto di fotogrammetria con webodm in modo da eliminare la necessita' di cercare a mano il centro dei tag e popolare il file gcp_list.txt

 


tag stampato su foglio A3
  

Ho fatto una prova con un Dji Matrice 4T con camera impostata a 12 Mpx a quota di 30 m e con tag stampato su foglio A3 (25 cm di lato). Con queste impostazioni siamo al limite del riconoscimento 

Come miglioramento si potrebbe salvare le immagini in raw e poi convertire in formato lossless invece di usare il jpg di default 

Si crea il seguente file usando i dati di un gps differenziale misurando il centro dei tag aruco  

gcp_rtk.csv 

id,easting,northing,elevation
5,652522.164,4842209.312,68.660
6,652542.163,4842206.497,68.626
2,652532.991,4842178.100,68.634
8,652547.155,4842150.228,68.728
4,652530.467,4842146.604,68.663

si lancia poi lo script per generare il file gcp_list.txt

detect_aruco.py 

#!/usr/bin/env python3
"""
Rileva i marker ArUco in tutte le immagini JPG di una cartella e salva:
  1) un CSV con: immagine, id, pixel centrale (cx, cy) a piena risoluzione
  2) (opzionale, con --gcp) un file gcp_list.txt per ODM/WebODM, unendo i pixel
     rilevati con le coordinate RTK lette da un CSV (id,easting,northing,elevation)

Formato gcp_list.txt (separato da TAB), come nel file di riferimento:
    EPSG:xxxx
    X  Y  Z  im_x  im_y  nome_immagine.JPG  etichetta

Uso:
    python detect_aruco.py                                  # solo CSV dei pixel
    python detect_aruco.py --gcp gcp_rtk.csv --epsg EPSG:32632
    python detect_aruco.py -i immagini -d DICT_4X4_50 --gcp gcp_rtk.csv --annotate

Requisiti: pip install opencv-contrib-python numpy
"""
import argparse
import csv
import sys
from pathlib import Path

import cv2
import numpy as np


def parse_ids(s):
    """'0-20,35,40' -> {0..20, 35, 40}; None se vuoto."""
    if not s:
        return None
    out = set()
    for part in s.split(","):
        if "-" in part:
            a, b = part.split("-")
            out.update(range(int(a), int(b) + 1))
        else:
            out.add(int(part))
    return out


def load_gcp_csv(path):
    """Legge il CSV RTK. Colonne: id, easting, northing, elevation (nomi tollerati,
    separatore virgola/punto e virgola/tab). Colonna opzionale 'label'.
    Ritorna {id: (x, y, z, label_o_None)}."""
    text = Path(path).read_text(encoding="utf-8-sig")
    first = text.splitlines()[0]
    delim = max([",", ";", "\t"], key=first.count)
    rows = list(csv.DictReader(text.splitlines(), delimiter=delim))
    if not rows:
        sys.exit(f"{path}: file vuoto")

    def col(row, names):
        low = {k.strip().lower(): v for k, v in row.items() if k}
        for n in names:
            if n in low and low[n] not in (None, ""):
                return low[n].strip()
        return None

    gcp = {}
    for r in rows:
        tid = col(r, ["id", "tag", "marker"])
        x = col(r, ["easting", "east", "x", "e"])
        y = col(r, ["northing", "north", "y", "n"])
        z = col(r, ["elevation", "quota", "z", "h", "height", "elev"])
        if None in (tid, x, y, z):
            sys.exit(f"{path}: riga non valida o colonne mancanti: {r}")
        gcp[int(tid)] = (float(x.replace(",", ".")), float(y.replace(",", ".")),
                         float(z.replace(",", ".")), col(r, ["label", "name", "nome"]))
    return gcp


def make_detector(dict_name):
    if not hasattr(cv2.aruco, dict_name):
        sys.exit(f"Dizionario sconosciuto: {dict_name}")
    dictionary = cv2.aruco.getPredefinedDictionary(getattr(cv2.aruco, dict_name))
    p = cv2.aruco.DetectorParameters()
    # Marker piccoli (riprese da drone): accetta perimetri ridotti
    p.minMarkerPerimeterRate = 0.005
    p.adaptiveThreshWinSizeMin = 3
    p.adaptiveThreshWinSizeMax = 63
    p.adaptiveThreshWinSizeStep = 6
    p.cornerRefinementMethod = cv2.aruco.CORNER_REFINE_SUBPIX
    # Riduce i falsi positivi su erba/recinzioni
    p.errorCorrectionRate = 0.3
    p.maxErroneousBitsInBorderRate = 0.25
    return cv2.aruco.ArucoDetector(dictionary, p)


def center_from_diagonals(p):
    a, b, c, d = p
    A = np.array([[c[0] - a[0], -(d[0] - b[0])], [c[1] - a[1], -(d[1] - b[1])]])
    rhs = np.array([b[0] - a[0], b[1] - a[1]])
    try:
        t, _ = np.linalg.solve(A, rhs)
        q = a + t * (c - a)
        return float(q[0]), float(q[1])
    except np.linalg.LinAlgError:
        m = p.mean(axis=0)
        return float(m[0]), float(m[1])


def detect(detector, gray, allowed_ids, min_side):
    """Ritorna lista di (id, cx, cy, lato_medio_px, corners 4x2)."""
    corners, ids, _ = detector.detectMarkers(gray)
    results = []
    if ids is None:
        return results
    for c, i in zip(corners, ids.flatten()):
        pts = c.reshape(4, 2)
        if allowed_ids is not None and int(i) not in allowed_ids:
            continue
        side = np.mean([np.linalg.norm(pts[k] - pts[(k + 1) % 4]) for k in range(4)])
        if side < min_side:
            continue
        cx, cy = center_from_diagonals(pts)  # corretto anche in prospettiva
        results.append((int(i), cx, cy, float(side), pts))
    return results


def main():
    ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
    ap.add_argument("-i", "--input", default="immagini", help="cartella con le immagini (default: immagini)")
    ap.add_argument("-o", "--output", default="aruco_tags.csv", help="CSV con i pixel rilevati")
    ap.add_argument("-d", "--dict", default="DICT_4X4_50", help="dizionario ArUco (default: DICT_4X4_50)")
    ap.add_argument("--ids", default=None,
                    help="ID ammessi, es. '0-20,35'. Con --gcp, di default sono gli ID presenti nel CSV RTK")
    ap.add_argument("--min-side", type=float, default=12.0, help="lato minimo del marker in px (default: 12)")
    ap.add_argument("--scale", type=float, default=1.0,
                    help="upscaling prima del rilevamento (es. 2); le coordinate restano a piena risoluzione")
    ap.add_argument("--annotate", action="store_true", help="salva immagini annotate in <output>_annotate/")
    ap.add_argument("-r", "--recursive", action="store_true", help="cerca anche nelle sottocartelle")
    g = ap.add_argument_group("GCP per ODM/WebODM")
    g.add_argument("--gcp", default=None, help="CSV RTK con colonne id,easting,northing,elevation")
    g.add_argument("--gcp-out", default="gcp_list.txt", help="file gcp di output (default: gcp_list.txt)")
    g.add_argument("--epsg", default="EPSG:32632",
                   help="riga di proiezione del gcp_list (default: EPSG:32632). "
                        "DEVE corrispondere al sistema delle coordinate RTK (es. EPSG:3003 per Gauss-Boaga)")
    g.add_argument("--prefix", default="GCP",
                   help="prefisso etichetta se il CSV non ha colonna 'label' (default: GCP -> GCP-05)")
    args = ap.parse_args()

    folder = Path(args.input)
    if not folder.is_dir():
        sys.exit(f"Cartella non trovata: {folder}")
    pattern = "**/*" if args.recursive else "*"
    files = sorted(f for f in folder.glob(pattern) if f.suffix.lower() in (".jpg", ".jpeg"))
    if not files:
        sys.exit(f"Nessuna immagine JPG in {folder}")

    gcp = load_gcp_csv(args.gcp) if args.gcp else None
    allowed = parse_ids(args.ids)
    if allowed is None and gcp is not None:
        allowed = set(gcp.keys())  # scarta automaticamente ID non presenti nel rilievo

    detector = make_detector(args.dict)
    ann_dir = None
    if args.annotate:
        base = Path(args.output).with_suffix("")
        ann_dir = base.parent / (base.name + "_annotate")
        ann_dir.mkdir(parents=True, exist_ok=True)

    n_tags = 0
    gcp_rows = []
    seen_ids = set()
    counts = {}
    with open(args.output, "w", newline="") as fh:
        w = csv.writer(fh)
        w.writerow(["image", "id", "cx", "cy", "side_px"])
        for k, f in enumerate(files, 1):
            img = cv2.imread(str(f))
            if img is None:
                print(f"[{k}/{len(files)}] {f.name}: impossibile leggere", file=sys.stderr)
                continue
            gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
            s = args.scale
            if s != 1.0:
                gray = cv2.resize(gray, None, fx=s, fy=s, interpolation=cv2.INTER_CUBIC)

            found = detect(detector, gray, allowed, args.min_side * s)
            for tid, cx, cy, side, pts in found:
                cx, cy, side, pts = cx / s, cy / s, side / s, pts / s
                w.writerow([f.name, tid, f"{cx:.2f}", f"{cy:.2f}", f"{side:.1f}"])
                n_tags += 1
                seen_ids.add(tid)
                counts[tid] = counts.get(tid, 0) + 1
                if gcp is not None:
                    x, y, z, label = gcp[tid]
                    label = label or f"{args.prefix}-{tid:02d}"
                    gcp_rows.append((x, y, z, cx, cy, f.name, label))
                if ann_dir is not None:
                    cv2.polylines(img, [pts.astype(np.int32).reshape(-1, 1, 2)], True, (0, 0, 255), 3)
                    cv2.circle(img, (int(round(cx)), int(round(cy))), 6, (0, 255, 255), -1)
                    cv2.putText(img, str(tid), (int(cx) + 12, int(cy) - 12),
                                cv2.FONT_HERSHEY_SIMPLEX, 1.5, (0, 0, 255), 3)
            if ann_dir is not None and found:
                cv2.imwrite(str(ann_dir / f.name), img)
            print(f"[{k}/{len(files)}] {f.name}: {len(found)} tag"
                  + (f" -> id {[t[0] for t in found]}" if found else ""))

    print(f"\nFatto: {n_tags} tag in {len(files)} immagini -> {args.output}")

    if gcp is not None:
        # Stesso formato del file di riferimento: riga EPSG + righe separate da TAB
        with open(args.gcp_out, "w", newline="") as fh:
            fh.write(args.epsg + "\n")
            for x, y, z, cx, cy, name, label in gcp_rows:
                fh.write(f"{x:.3f}\t{y:.3f}\t{z:.3f}\t{cx:.2f}\t{cy:.2f}\t{name}\t{label}\n")
        print(f"GCP: {len(gcp_rows)} righe -> {args.gcp_out} ({args.epsg})")
        missing = sorted(set(gcp) - seen_ids)
        if missing:
            print(f"Attenzione: ID presenti nel CSV RTK ma mai rilevati nelle immagini: {missing}")
        poor = sorted(t for t, n in counts.items() if n < 3)
        if poor:
            print(f"Attenzione: ID visti in meno di 3 immagini (GCP poco vincolanti): {poor}")


if __name__ == "__main__":
    main()


il risultato e' qualcosa di simile 

gcp_list.txt 

EPSG:32632
652522.164    4842209.312    68.660    3749.82    1573.28    DJI_20261012113902_0011_V.JPG    GCP-05
652522.164    4842209.312    68.660    2205.37    73.27    DJI_20261012113904_0012_V.JPG    GCP-05
652530.467    4842146.604    68.663    3541.01    2929.11    DJI_20261012113956_0036_V.JPG    GCP-04
652532.991    4842178.100    68.634    3644.55    2421.14    DJI_20261012114004_0040_V.JPG    GCP-02
652522.164    4842209.312    68.660    2486.07    1970.55    DJI_20261012114013_0044_V.JPG    GCP-05
652522.164    4842209.312    68.660    2481.56    2593.47    DJI_20261012114015_0045_V.JPG    GCP-05
652542.163    4842206.497    68.626    442.20    131.14    DJI_20261012114030_0052_V.JPG    GCP-06
652522.164    4842209.312    68.660    2311.71    1630.05    DJI_20261012114035_0054_V.JPG    GCP-05
652530.467    4842146.604    68.663    1249.13    55.82    DJI_20261012114050_0061_V.JPG    GCP-04
652530.467    4842146.604    68.663    1926.51    2244.16    DJI_20261012114141_0085_V.JPG    GCP-04
652530.467    4842146.604    68.663    1910.85    2873.87    DJI_20261012114143_0086_V.JPG    GCP-04
652532.991    4842178.100    68.634    2020.32    2358.83    DJI_20261012114152_0090_V.JPG    GCP-02
652532.991    4842178.100    68.634    2006.84    2989.47    DJI_20261012114154_0091_V.JPG    GCP-02
652542.163    4842206.497    68.626    2020.37    2281.77    DJI_20261012114222_0104_V.JPG    GCP-06

 

 


 

 

sabato 10 ottobre 2026

Ricerca immagini Enmap

La ricerca di EoWeb non mi hai soddisfatto moltissimo

Ho provato a chiedere a Claude un tool per avere una selezione di immagini per posizione geografia, data e copertura nuvolosa

 


 e non mi ero mai accorto di quanto fosse cosi' poco diffusa la copertura di Enmap (che non ha una acquisizione continua ma solo on demand)

 

"""
Download EnMAP L2A per l'Italia in agosto, con filtro nuvolosità lato client,
calcolo della copertura per anno e cumulata, e export dei buchi.

Installazione:
    pip install pystac-client geopandas shapely requests pyogrio

Credenziali EOWEB (PowerShell, poi riapri il terminale):
    setx EOWEB_USER "tuo_utente"
    setx EOWEB_PASS "tua_password"
"""
import os
import sys
import warnings
import requests
import geopandas as gpd
from pystac_client import Client
from shapely.geometry import shape
from shapely.ops import unary_union

warnings.filterwarnings("ignore", message=".*does not conform to.*")

# ---------------- CONFIGURAZIONE ----------------
STAC_URL   = "https://geoservice.dlr.de/eoc/ogc/stac/v1/"
COLLECTION = "ENMAP_HSI_L2A"          # verificata a runtime (vedi sotto)
BBOX       = [6.6, 35.4, 18.6, 47.2]  # Italia con isole
YEARS      = [2023, 2024, 2025]       # il 2022 era in commissioning (poche scene)
MONTH_FROM = "01-01"
MONTH_TO   = "12-31"
MAX_CLOUD  = 5                         # % nuvolosità massima
OUT_DIR    = "enmap"
DRY_RUN    = True                      # True = solo analisi, nessun download
ASSET_KEYS = None                      # es. ["spectral_image"]; None = li stampa e basta
# -------------------------------------------------

NE_URL = ("https://naturalearth.s3.amazonaws.com/50m_cultural/"
          "ne_50m_admin_0_countries.zip")


def get_italy():
    world = gpd.read_file(NE_URL)
    col = "ADMIN" if "ADMIN" in world.columns else "admin"
    italy = world[world[col] == "Italy"].to_crs(3035)
    geom = unary_union(italy.geometry.values)
    return geom


def cloud_of(item):
    return item.properties.get("eo:cloud_cover")


def cloud_ok(item):
    cc = cloud_of(item)
    return cc is not None and cc <= MAX_CLOUD


def footprint_3035(items):
    g = gpd.GeoSeries([shape(i.geometry) for i in items], crs=4326).to_crs(3035)
    return unary_union(g.values)


def main():
    cat = Client.open(STAC_URL)

    # controllo collezione
    ids = [c.id for c in cat.get_collections()]
    enmap_ids = [c for c in ids if "ENMAP" in c.upper()]
    print("Collezioni EnMAP disponibili:", enmap_ids)
    coll = COLLECTION if COLLECTION in ids else (enmap_ids[0] if enmap_ids else None)
    if coll is None:
        sys.exit("Nessuna collezione EnMAP trovata nel catalogo.")
    if coll != COLLECTION:
        print(f"ATTENZIONE: uso '{coll}' invece di '{COLLECTION}'")

    italy = get_italy()
    italy_area = italy.area

    selected = {}      # anno -> lista item filtrati
    rows = []          # per export footprint

    for y in YEARS:
        search = cat.search(
            collections=[coll],
            bbox=BBOX,
            datetime=f"{y}-{MONTH_FROM}/{y}-{MONTH_TO}",
            max_items=5000,
        )
        items = list(search.items())
        n_tot = len(items)
        n_nocc = sum(cloud_of(i) is None for i in items)
        good = [i for i in items if cloud_ok(i)]
        selected[y] = good

        print(f"\n{y}: {n_tot} scene totali | "
              f"{len(good)} con nuvole <= {MAX_CLOUD}% | "
              f"{n_nocc} senza valore nuvolosità")

        if n_tot and n_nocc == n_tot:
            print("  -> il campo 'eo:cloud_cover' non esiste. Proprietà disponibili:")
            print("  ", sorted(items[0].properties.keys()))

        if good:
            cov = footprint_3035(good).intersection(italy)
            print(f"  copertura Italia: {cov.area / italy_area:.1%}")
        else:
            print("  copertura Italia: 0%")

        for i in good:
            rows.append({
                "id": i.id, "anno": y,
                "data": str(i.datetime), "nuvole": cloud_of(i),
                "geometry": shape(i.geometry),
            })

    all_items = [i for its in selected.values() for i in its]
    if not all_items:
        sys.exit("\nNessuna scena selezionata: alza MAX_CLOUD o allarga le date.")

    # copertura cumulata e buchi
    cov_all = footprint_3035(all_items).intersection(italy)
    print(f"\nCopertura cumulata {YEARS[0]}-{YEARS[-1]}: "
          f"{cov_all.area / italy_area:.1%}  ({len(all_items)} scene)")

    gaps = italy.difference(cov_all)
    gpd.GeoDataFrame({"tipo": ["buco"]}, geometry=[gaps], crs=3035)\
       .to_file("buchi_italia.gpkg", driver="GPKG")
    gpd.GeoDataFrame(rows, crs=4326).to_file("footprint_scene.gpkg", driver="GPKG")
    print("Scritti: buchi_italia.gpkg, footprint_scene.gpkg (aprili in QGIS)")

    # asset disponibili
    print("\nAsset della prima scena:")
    for k, a in all_items[0].assets.items():
        print(f"  {k:25s} {a.href}")

    if DRY_RUN:
        print("\nDRY_RUN attivo: nessun download. Imposta DRY_RUN = False "
              "e ASSET_KEYS dopo aver guardato gli asset sopra.")
        return
    if not ASSET_KEYS:
        sys.exit("Imposta ASSET_KEYS con le chiavi degli asset da scaricare.")

    # download
    user, pwd = os.environ.get("EOWEB_USER"), os.environ.get("EOWEB_PASS")
    if not user or not pwd:
        sys.exit("Imposta EOWEB_USER e EOWEB_PASS.")
    os.makedirs(OUT_DIR, exist_ok=True)
    sess = requests.Session()
    sess.auth = (user, pwd)

    for it in all_items:
        for key in ASSET_KEYS:
            asset = it.assets.get(key)
            if asset is None:
                continue
            ext = os.path.splitext(asset.href.split("?")[0])[1] or ".bin"
            out = os.path.join(OUT_DIR, f"{it.id}_{key}{ext}")
            if os.path.exists(out):
                print("già presente:", out)
                continue
            print("scarico:", out)
            tmp = out + ".part"
            with sess.get(asset.href, stream=True, timeout=120) as r:
                r.raise_for_status()
                with open(tmp, "wb") as f:
                    for chunk in r.iter_content(1 << 20):
                        f.write(chunk)
            os.replace(tmp, out)


if __name__ == "__main__":
    main()

 

 

 

 

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
 

 

 




Coregistrazione immagini satellitari con AROSIC

Nonostante le immagini satellitare siano gia' georiferite per fare analisi multitemporale a livello pixel serve che le immagini si sovra...