Questo quaderno è la seconda parte (cfr. per la prima) in cui vengono descritti una serie di problemi
I dati sono costituiti dagli HBOOK
convertiti in ROOT
usando il comando h2root
e quindi concatenati.
Il lavoro è stato fatto col seguente script
#!/bin/bash
for f in $(ls ./hbook/*.hbook); do
# Solo nome (e.g. run016491_001.hbook)
bb=$(basename $f)
# Prima parte del nome (e.g. run016491)
nn=$(echo $bb | cut -d'_' -f1)
# Nome completo no estensione (e.g. run016491_001)
nome=${bb/.hbook/}
# Root singolo (e.g. ./root/run016491_001.root)
fileParziale=./root/${nome}.root
# Root unito (e.g. ./root_merged/run016491.root)
fileTotale=./root_merged/${nn}.root
h2root $f $fileParziale
hadd -a $fileTotale $fileParziale
done
La guida di riferimento è questo mio altro esempio realizzato per il test beam di NanoCal, le spiegazioni dettagliate si trovano lì
import sys, os, re, glob
import uproot
import numpy as np
from matplotlib import pyplot as plt
dataPath = r"/eos/project/i/insulab-como/testBeam/CNAO_2023_06/root_merged"
dataPath = r"/eos/project/i/insulab-como/testBeam/CNAO_2023_06/root"
numRun = 16496
fileToLoad = os.path.join(dataPath, f"run{numRun:06d}_001.root")
with uproot.open(fileToLoad) as f:
for k in f:
print(k)
try:
for kk in f[k]:
print(kk)
except:
pass
h11;1 <TBranch 'I15h1' at 0x7fa59a81bca0> <TBranch 'Itime' at 0x7fa598f5f520> <TBranch 'Iatime' at 0x7fa598f5fcd0> <TBranch 'Iutil' at 0x7fa598f664c0> <TBranch 'Nword0_742' at 0x7fa598f66c70> <TBranch 'Idata0_742' at 0x7fa598f6b460> <TBranch 'Info_onl' at 0x7fa598f6bc10> h1;1 <TBranch 'I15g1' at 0x7fa598f71670> <TBranch 'Itime' at 0x7fa598f78280> <TBranch 'Iatime' at 0x7fa598f78a30> <TBranch 'Iutil' at 0x7fa598f7e220> <TBranch 'Nword0_730' at 0x7fa598f7e9d0> <TBranch 'Idata0_730' at 0x7fa598f831c0> <TBranch 'Info_onl' at 0x7fa598f83970> h2;1 <TBranch 'I15g1' at 0x7fa598f898b0> <TBranch 'Itime' at 0x7fa598f8e130> <TBranch 'Iatime' at 0x7fa598f8e8e0> <TBranch 'Iutil' at 0x7fa598f930d0> <TBranch 'Nword0_730' at 0x7fa598f93880> <TBranch 'Idata0_730' at 0x7fa598f93fa0> <TBranch 'Info_onl' at 0x7fa598f98820> h3;1 <TBranch 'I15g1' at 0x7fa598f20220> <TBranch 'Itime' at 0x7fa598f20cd0> <TBranch 'Iatime' at 0x7fa598f274c0> <TBranch 'Iutil' at 0x7fa598f27c70> <TBranch 'Nword0_730' at 0x7fa598f2b460> <TBranch 'Idata0_730' at 0x7fa598f2bc10> <TBranch 'Info_onl' at 0x7fa598f32400> h12;1 <TBranch 'I15h1' at 0x7fa598f37190> <TBranch 'Itime' at 0x7fa598f379d0> <TBranch 'Iatime' at 0x7fa598f3e1c0> <TBranch 'Iutil' at 0x7fa598f3e970> <TBranch 'Nword0_742' at 0x7fa598f42160> <TBranch 'Idata0_742' at 0x7fa598f42910> <TBranch 'Info_onl' at 0x7fa598f49100> h13;1 <TBranch 'I15h1' at 0x7fa598f499d0> <TBranch 'Itime' at 0x7fa598f4e700> <TBranch 'Iatime' at 0x7fa598f4eeb0> <TBranch 'Iutil' at 0x7fa598f536a0> <TBranch 'Nword0_742' at 0x7fa598f53e50> <TBranch 'Idata0_742' at 0x7fa598f5a640> <TBranch 'Info_onl' at 0x7fa598f5adf0> h4;1 <TBranch 'I15g1' at 0x7fa598ee0730> <TBranch 'Itime' at 0x7fa598ee5430> <TBranch 'Iatime' at 0x7fa598ee5be0> <TBranch 'Iutil' at 0x7fa598eeb3d0> <TBranch 'Nword0_730' at 0x7fa598eebb80> <TBranch 'Idata0_730' at 0x7fa598ef0370> <TBranch 'Info_onl' at 0x7fa598ef0b20> h110000;1 h120000;1 h5101;1 h5251;1 h5151;1 h5102;1 h5252;1 h5152;1 h5103;1 h5253;1 h5153;1 h5104;1 h5254;1 h5154;1 h5105;1 h5255;1 h5155;1 h5106;1 h5256;1 h5156;1 h5107;1 h5257;1 h5157;1 h5108;1 h5258;1 h5158;1
Io ipotizzo che sia h11
, ma lo dimostriamo
with uproot.open(fileToLoad) as f:
f["h11"].show()
wf = f["h11"]["Idata0_742"].array(library = "np")
nw = f["h11"]["Nword0_742"].array(library = "np")
print(wf.shape, nw.shape, )
print(wf[0].shape, nw[0].shape, )
name | typename | interpretation ---------------------+--------------------------+------------------------------- I15h1 | int32_t | AsDtype('>i4') Itime | int32_t | AsDtype('>i4') Iatime | int32_t | AsDtype('>i4') Iutil | int32_t[16] | AsDtype("('>i4', (16,))") Nword0_742 | int32_t | AsDtype('>i4') Idata0_742 | int32_t[] | AsJagged(AsDtype('>i4')) Info_onl | uint16_t[100] | AsDtype("('>u2', (100,))") (500,) (500,) (32992,) ()
Nword0_742
¶Contiene il numero di parole, ovvero il "numero di colonne" che compongono la corrispondente. Questo valore è sempre uguale a $32992$, come si evince dal grafico sopra
Una singola waveform è costituita da 1024 campionamenti, preceduti da 5 valori da saltare, e seguiti da altri 2 valori da saltare, per un totale di 1031 punti. Il digitizer V1742
ha 32 canali, quindi si avrà una ripetizione di 32 volte questa unità elementare $1031 * 32 = 32992 $.
In realtà vengono letti due digitizer, un odoscopio ha infatti 64 canali. Quindi ci sono $1031*64$ parole, dove parole da 16 bit vengono a due a due compattate in una parola da 32 bit.
Idata0_742
¶Ora la matrice Idata0_742
contiene $500$ righe, una per evento e $32992 = 32 * 1031$ colonne, una volta che ho trasformato l'array di array in una matrice. Le colonne sono da interpretare come la ripetizione della seguente unità elementare
Questa struttura di $1031$ parole si ripete per ciascun canale.
Come già osservato, i dati provenienti dal digitizer V1742
hanno risoluzione a 12 bit, per la maggior parte dei digitizer invece a 14 bit. Si potrebbe sicuramente osservare che il tipo di dato int
, ovvero intero a 32 bit sia eccessivo. La cosa che più balza all'occhio è l'asse verticale completamente sballato del quaderno precedente.
La chiave, trovata da Valerio, è che ciascun intero a 32 bit è da interpretare come due interi a 16 bit giustapposti. Quella che vedavamo non era altro che la rappresentazione decimale di un numero a 32 bit, ma che avrebbe dovuto essere interpretato come due interi a 16 bit!
# Trasformo da array di array a matrice
waveform = np.stack(wf, axis = 0)
print(waveform.shape)
(500, 32992)
# Creo una terza dimensione per avere indice di evento e indice di canale
waveform2 = waveform[:, np.newaxis, :]
print(waveform2.shape)
(500, 1, 32992)
# Effettuo il reshaping (evento, canale, tempo)
waveformMatrix = waveform2.reshape(waveform.shape[0], 32, 1031)
print(waveformMatrix.shape)
(500, 32, 1031)
least = np.bitwise_and(waveformMatrix[:,:,:], 0xFFFF)
most = np.right_shift(waveformMatrix[:,:,:], 16)
least.shape, most.shape
((500, 32, 1031), (500, 32, 1031))
Attualmente mi trovo con due matrici, aventi il primo indice come quasi numero di evento, il secondo indice rappresenta il canale e il terzo indice il campionamento, dove al solito
Una riga di Idata0_742
contiene le informazioni relative a 64 canali, ovvero due digitizer
A questo punto dovrei pescare un elemento da most e uno da least. Alla fine avrò una matrice col doppio dei canali
In realtà inizio a mettere le due parole su una riga, una riga avrà dunque due eventi
# Inizializzo la matrice temporanea, con due waveform per riga
wfMatrix = np.empty((least.shape[0], least.shape[1], least.shape[2]*2), dtype = "int16")
print(wfMatrix.shape, wfMatrix.dtype)
(500, 32, 2062) int16
# Lista per inserire i campionamenti pari
evenIdx = 2 * np.arange(0, least.shape[2], 1, dtype = "int")
print(evenIdx)
[ 0 2 4 ... 2056 2058 2060]
# Lista per inserire i campionamenti dispari
oddIdx = 2 * np.arange(0, least.shape[2], 1, dtype = "int") + 1
print(oddIdx)
[ 1 3 5 ... 2057 2059 2061]
# Check che tutto sia corretto
print(wfMatrix[:,:,evenIdx].shape) # Una delle due matrici in cui vado ad inserire
print(most.shape) # Matrice che vado ad inserire
(500, 32, 1031) (500, 32, 1031)
Questo punto meriterebbe una discussione dettagliata. Io, ignorantemente mi aspettavo che si riempisse most-least, esattamente come leggo un numero scritto su un foglio. Empiricamente, ho verificato che pur tagliando nel modo giusto, tutte le waveform presentavano un primo punto a zero, e qualcosa di strano alla fine. Invertendo l'ordine, invece le waveform hanno proprio la faccia che devono avere.
Il lettore curioso può invertire e scoprire cosa succede
# Alterno most0-least0-most1-least1....
# wfMatrix[:,:,evenIdx] = most
# wfMatrix[:,:,oddIdx] = least
wfMatrix[:,:,evenIdx] = least
wfMatrix[:,:,oddIdx] = most
# La struttura ora è: 5 - 1024 - 2 - 5 - 1024 - 2
# Mi serve dunque 5:1024+5 U 5+1024+2+5:5+1024+2+5+1024
a = np.arange(5, 1024+5)
print(a.shape)
b = np.arange(5+1024+2+5, 5+1024+2+5+1024)
print(b.shape)
idxToKeep = np.concatenate((a,b))
print(idxToKeep.shape)
# Matrice a cui rimuovo header and trailer
wfMatrixCrop = wfMatrix[:,:,idxToKeep]
print(wfMatrix.shape)
print(wfMatrixCrop.shape)
(1024,) (1024,) (2048,) (500, 32, 2062) (500, 32, 2048)
Costruisco dunque una matrice col doppio di eventi e metà colonne, e con lo stesso trucchetto alterno eventi pari ed eventi dispari
# Inizializzo la matrice finale
wfMatrixFinal = np.empty((wfMatrixCrop.shape[0], wfMatrixCrop.shape[1] * 2, int(wfMatrixCrop.shape[2]/2)), dtype = "int16")
print(wfMatrixFinal.shape, wfMatrixFinal.dtype)
(500, 64, 1024) int16
# Lista per popolare gli eventi pari
evenIdxRow = 2 * np.arange(0, wfMatrixCrop.shape[1], 1, dtype = "int")
print(evenIdxRow)
[ 0 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 32 34 36 38 40 42 44 46 48 50 52 54 56 58 60 62]
# Lista per popolare gli eventi dispari
oddIdxRow = 2 * np.arange(0, wfMatrixCrop.shape[1], 1, dtype = "int") + 1
print(oddIdxRow)
[ 1 3 5 7 9 11 13 15 17 19 21 23 25 27 29 31 33 35 37 39 41 43 45 47 49 51 53 55 57 59 61 63]
# Notare attentamente la posizione dei : nella terza dimensione..
wfMatrixFinal[:,evenIdxRow,:] = wfMatrixCrop[:,:,:wfMatrixFinal.shape[2]]
wfMatrixFinal[:,oddIdxRow,:] = wfMatrixCrop[:,:,wfMatrixFinal.shape[2]:]
print(wfMatrixFinal.shape)
(500, 64, 1024)
# Verifico che siano coerenti con un digitizer a 12 bit...
print(wfMatrixFinal.min())
print(wfMatrixFinal.max())
print(wfMatrixFinal.mean())
print(wfMatrixFinal.std())
1457 3693 3501.9082320861817 327.5669701957176
Piuttosto inutile, l'ho usato per stabilire il corretto ordine least-most
print(wfMatrix.shape)
idxEv = 11
fig, ax = plt.subplots(8,4)
ax = ax.flatten()
fig.set_size_inches(60, 40)
for idxChan in range(len(ax)):
ax[idxChan].plot(wfMatrix[idxEv, idxChan, :])
ax[idxChan].set_xlabel("Time [tick]")
ax[idxChan].set_ylabel("Voltage [ADC]")
ax[idxChan].set_title(f"Chan. {idxChan}", fontsize = 20)
plt.show()
(500, 32, 2062)
idxEv = 10
fig, ax = plt.subplots(16,4)
ax = ax.flatten()
fig.set_size_inches(80, 40)
for idxChan in range(len(ax)):
ax[idxChan].plot(wfMatrixFinal[idxEv, idxChan, :], c = "tab:green")
ax[idxChan].set_xlabel("Time [tick]")
ax[idxChan].set_ylabel("Voltage [ADC]")
ax[idxChan].set_title(f"Chan. {idxChan}", fontsize = 20)
plt.show()
# Anche questa cella è stata usata per debuggare l'alternanza least-most
print(wfMatrixFinal[10, 20, :] < 50)
print(wfMatrixFinal[10, 20, :])
print(wfMatrixFinal[10, 20, :].shape)
print()
print(wfMatrix[5, 20, :] < 50)
print(wfMatrix[5, 20, :])
print(wfMatrix[5, 20, :].shape)
[False False False ... False False False] [3531 3531 3533 ... 3540 3539 3538] (1024,) [False False False ... False False False] [2056 1024 697 ... 3538 2639 2057] (2062,)
idxEv = 10
idxChan = 8
fig, ax = plt.subplots()
fig.set_size_inches(12, 5)
ax.plot(np.arange(0,wfMatrixFinal.shape[2],1), wfMatrixFinal[idxEv, idxChan, :], c = "tab:green")
ax.set_xlabel("Time [tick]")
ax.set_ylabel("Voltage [ADC]")
ax.set_title(f"Chan. {idxChan}", fontsize = 20)
plt.show()
# Ispezione le prime 6 componenti di tutte le waveform
# 0-1-2-3-4 sono l'header
fig, ax = plt.subplots(1,6, figsize=(21,3))
for i in range(6):
ax[i].hist(wfMatrix[:,:,i].flatten(), 100)
ax[i].set_title(f"pos. {i}")
plt.show()
# 6 componenti successive: sono tutti "dati"
fig, ax = plt.subplots(1,6, figsize=(21,3))
for i in range(6):
pos = i+6
ax[i].hist(wfMatrix[:,:,pos].flatten(), 100)
ax[i].set_title(f"pos. {pos}")
plt.show()
# 1029-1030 sono la fine del primo digitizer
# 1031-1032-1033-1034-1035 sono l'header del secondo digitizer
fig, ax = plt.subplots(1,6, figsize=(21,3))
for i in range(6):
pos = i+1028
ax[i].hist(wfMatrix[:,:,pos].flatten(), 100)
ax[i].set_title(f"pos. {pos}")
plt.show()
# 1031-1032-1033-1034-1035 sono l'header del secondo digitizer
fig, ax = plt.subplots(1,6, figsize=(21,3))
for i in range(6):
pos = i+1028+6
ax[i].hist(wfMatrix[:,:,pos].flatten(), 100)
ax[i].set_title(f"pos. {pos}")
plt.show()
# Gli ultimi due sono l'end del secondo digitizer
fig, ax = plt.subplots(1,6, figsize=(21,3))
for i in range(6):
pos = -i
if pos == 0: continue
ax[i].hist(wfMatrix[:,:,pos].flatten(), 100)
ax[i].set_title(f"pos. {pos}")
plt.show()
wfMatrixFinal.dtype
dtype('int16')