giovedì 28 maggio 2026

FigSpec FS-60CL

A lavoro mi hanno rifilato questo sensore iperspettrale cinese (pushbroom 400-1000 nm con larghezza di banda di 0.5 nm compatibile con DJI Matrice 400). Rifilato perche' di questo sensore esistono poche notizie e praticamente nessuna pubblicazione scientifica 


Il sensore viene fornito con un bianco di riferimento riflettente all'80%. Non si tratta di uno spectralon ma di tipo una stuoia pieghievole con una vernice su un lato

La calibrazione del bianco di riferimento viene fornita in un file csv

80%反射率校准布系数(Reflectance Calibration Cloth).figspecref

 

import matplotlib.pyplot as plt
import numpy as np

file_path = "80%反射率校准布系数(Reflectance Calibration Cloth).figspecref"

wavelengths = []
reflectivities = []

# Leggi il file saltando le righe di intestazione o vuote
with open(file_path, "r", encoding="utf-8") as f:
for line in f:
# Pulisci gli spazi e rimuovi i caratteri di fine riga
cleaned_line = line.strip()
# Salta la riga se è vuota o contiene testo di intestazione
if not cleaned_line or "Wavelength" in cleaned_line:
continue
try:
# Dividi la riga usando la virgola
parts = [p.strip() for p in cleaned_line.split(",") if p.strip()]
if len(parts) >= 2:
wavelengths.append(float(parts[0]))
reflectivities.append(float(parts[1]))
except ValueError:
# Salta eventuali righe che non contengono numeri convertibili
continue

# Converti in array NumPy per comodità
wavelengths = np.array(wavelengths)
reflectivities = np.array(reflectivities)

# ==========================================
# PLOTTING DELLA CURVA DI CALIBRAZIONE
# ==========================================
plt.figure(figsize=(12, 6))

# Plot principale della curva
plt.plot(wavelengths, reflectivities, color="crimson", lw=2.5, label="Telo di Calibrazione (~80%)")

# Personalizzazione estetica del grafico
plt.title("Curva di Riflettanza Nominale del Telo di Calibrazione", fontsize=14, fontweight='bold', pad=15)
plt.xlabel("Lunghezza d'onda (nm)", fontsize=12)
plt.ylabel("Riflettività (%)", fontsize=12)

# Griglia millimetrata di sfondo
plt.grid(True, linestyle=":", alpha=0.6)
plt.legend(fontsize=11, loc="lower left")

# Imposta i limiti degli assi basandoti esattamente sui dati del file
plt.xlim(wavelengths.min(), wavelengths.max())
plt.ylim(0, 100) # La riflettività massima teorica è 100%

# Mostra il grafico a schermo
plt.tight_layout()
plt.show()

Come si vede dal grafico la curva e' tutto fuorche' piatta 

Il problema e' che il file di calibrazione ha un passo di 10 nm mentre il sensore acquisisce a 0.5 nm

La procedura prevede pre volo di acquisire la dark current semplicemente tappando l'ottica ed un bianco inquadrando a terra il telo bianco di riferimento

Vengono generati due file figspec.figspecwhite e figspec.figspecblack. Questi sono due file raw finari int16 (il contenuto varia a seconda del tipo di acquisizione ,..in questo caso e' stato utilizzato 1/4 della massima risoluzione spaziale e spettral)

import numpy as np
import matplotlib.pyplot as plt

# ==========================================
# 1. PARAMETRI HARDCODED (Dati dai passaggi precedenti)
# ==========================================
white_file = "figspec.figspecwhite"
dark_file = "figspec.figspecblack"

# Sappiamo dalle letture precedenti che il sensore ha queste dimensioni:
width = 480
bands = 300
frame_size = bands * width # 144,000 elementi per singolo frame completo

# ==========================================
# 2. LEGGI E PARSA IL WHITE REFERENCE
# ==========================================
white_raw = np.fromfile(white_file, dtype=np.int16)
white_lines = len(white_raw) // frame_size
# Tagliamo dall'orlo per evitare eventuali byte di header binario iniziale
white_clean = white_raw[-white_lines * frame_size:]
white_cube = white_clean.reshape((white_lines, bands, width))

# Passaggio chiave per lo spettro:
# 1. Facciamo la media lungo i frame (lines) per pulire il rumore temporale -> (bands, width)
# 2. Facciamo la media lungo la larghezza (width) per ottenere un unico profilo medio -> (bands,)
white_spectrum = np.mean(white_cube, axis=(0, 2))

# ==========================================
# 3. LEGGI E PARSA IL DARK CURRENT
# ==========================================
dark_raw = np.fromfile(dark_file, dtype=np.int16)
dark_lines = len(dark_raw) // frame_size
dark_clean = dark_raw[-dark_lines * frame_size:]
dark_cube = dark_clean.reshape((dark_lines, bands, width))

# Stessa operazione di media per il Dark Current -> (bands,)
dark_spectrum = np.mean(dark_cube, axis=(0, 2))

# ==========================================
# 4. PLOTTING DEGLI SPETTRI
# ==========================================
# Creiamo l'asse X basato sul numero di canali/bande (da 1 a 300)
wavelength_axis = np.arange(1, bands + 1)

plt.figure(figsize=(12, 6))

# Plot della linea Bianca
plt.plot(wavelength_axis, white_spectrum, label="White Reference (Riflessione Max)", color="gold", lw=2)

# Plot della linea Nera
plt.plot(wavelength_axis, dark_spectrum, label="Dark Current (Rumore di Fondo)", color="black", lw=1.5, linestyle="--")

# Personalizzazione del Grafico
plt.title("Profili Spettrali di Calibrazione (White vs Dark)", fontsize=14, fontweight='bold')
plt.xlabel("Numero di Banda (Canale Spettrale)", fontsize=12)
plt.ylabel("Intensità / Digital Numbers (DN)", fontsize=12)
plt.grid(True, linestyle=":", alpha=0.6)
plt.legend(fontsize=11, loc="upper right")

# Un piccolo aggiustamento grafico per non far toccare i bordi alle curve
plt.xlim(1, bands)

# Mostra il grafico
plt.tight_layout()
plt.show()


 


 Le immagini vengono salvate in un file .spe che in realta' e' un Envi file in formato BIL con dati uint16. Nel file header ci sono anche i metadati della quota di volo ed il tempo di espozione


Nessun commento:

Posta un commento

FigSpec FS-60CL

A lavoro mi hanno rifilato questo sensore iperspettrale cinese (pushbroom 400-1000 nm con larghezza di banda di 0.5 nm compatibile con DJI M...