Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Containers for waveforms and charge extraction #29

Merged
merged 28 commits into from
Jan 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
fe83824
-container for event charge and waveform
guillaumegrolleron Oct 24, 2022
ef1f873
-logging container
guillaumegrolleron Nov 9, 2022
f3c2f45
-user script for charge computation
guillaumegrolleron Nov 9, 2022
1ff2ace
-add test to gitignore + VsCode files
guillaumegrolleron Nov 9, 2022
7fe6790
-container for waveforms and charge computation
guillaumegrolleron Dec 14, 2022
98ffb0d
-user scripts for ggrolleron
guillaumegrolleron Dec 14, 2022
e1fa3d0
-bugfix if pixels id don't match npixels
guillaumegrolleron Dec 14, 2022
ecda309
-user scripts for ggrolleron
guillaumegrolleron Dec 14, 2022
111d71d
-container for waveforms and charge computation
guillaumegrolleron Dec 14, 2022
4b3f2e8
-container updates
guillaumegrolleron Dec 16, 2022
63cc891
-add charge extractor from Federica
guillaumegrolleron Dec 16, 2022
f5dcc99
-add I/O for waveform container
guillaumegrolleron Dec 19, 2022
18ac065
-user scripts
guillaumegrolleron Dec 19, 2022
23761fa
-add WaveformsContainers and ChargeContainers
guillaumegrolleron Dec 21, 2022
7d83f9e
bugfix : trigger_patern seems to be
guillaumegrolleron Jan 4, 2023
d9f5b4d
-follow up previous commit : trigger patern shape
guillaumegrolleron Jan 4, 2023
5f7ad79
-bugfix : replace np.empty by np.zeros
guillaumegrolleron Jan 5, 2023
d75ae25
-bugfix : trigger_patern_all axis change
guillaumegrolleron Jan 6, 2023
41faa51
-bugfix : in histo low gain and high gain
guillaumegrolleron Jan 6, 2023
a0b6d48
-bugfix : follow up trigger_patern_all axis change
guillaumegrolleron Jan 6, 2023
48cdf70
-bugfix : event waveform access with
guillaumegrolleron Jan 6, 2023
db29945
-add kwargs propagation for ctapipe charge extractor
guillaumegrolleron Jan 18, 2023
c36fd6e
-SPE import useless in user script
guillaumegrolleron Jan 18, 2023
82a7331
-rewriting of load_wfs_compute_charge user script
guillaumegrolleron Jan 19, 2023
71b20bc
-clean+documentation
guillaumegrolleron Jan 23, 2023
9fd86ae
-Gain channels are now loaded from
guillaumegrolleron Jan 23, 2023
9d5c23f
-clean
guillaumegrolleron Jan 23, 2023
b9efcad
-user script : use argparse to read arguments
guillaumegrolleron Jan 23, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -86,4 +86,11 @@ target
notebooks/*.html
notebooks/*.png

nectarchain/calibration/test
**.log
**.pdf
**.csv

#VScode
.vscode/

7 changes: 7 additions & 0 deletions nectarchain/calibration/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Environment variable needed:
export NECTARCAMDATA="path to local NectarCam data, it can contain fits.fz run files, WaveformsContainer or ChargeContainer FITS files"

Environment variables which can be defined:
export NECTARCHAIN_TEST="path to test for nectarchain"
export NECTARCHAIN_LOG="path to log for nectarchain"
export NECTARCHAIN_FIGURES="path to log figures for nectarchain"
2 changes: 2 additions & 0 deletions nectarchain/calibration/container/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from .waveforms import *
from .charge import *
348 changes: 348 additions & 0 deletions nectarchain/calibration/container/charge.py

Large diffs are not rendered by default.

95 changes: 95 additions & 0 deletions nectarchain/calibration/container/charge_extractor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
from ctapipe.image import ImageExtractor
import numpy as np
import logging
from scipy.interpolate import InterpolatedUnivariateSpline
from scipy.signal import find_peaks

from numba import guvectorize

logging.basicConfig(format='%(asctime)s %(name)s %(levelname)s %(message)s')
log = logging.getLogger(__name__)
log.handlers = logging.getLogger('__main__').handlers

__all__ = ['gradient_extractor']

class gradient_extractor(ImageExtractor) :


fixed_window=np.uint8(16)
height_peak=np.uint8(10)

def __call__(self, waveforms, telid, selected_gain_channel,substract_ped = False):

shape = waveforms.shape
waveforms = waveforms.reshape(shape[0]*shape[1],shape[2])

if substract_ped :
log.info('substracting pedestal')
#calculate pedestal
ped_mean = np.mean(waveforms[:,:16],axis = 1).reshape(shape[0]*shape[1],1)

y = waveforms - (ped_mean)@(np.ones(1,shape[2])) # waveforms without pedestal
else :
log.info('do not substract pedestal')
y = waveforms

waveforms.reshape(shape[0],shape[1],shape[2])

peak_time, charge_integral, charge_sum, charge_integral_fixed_window, charge_sum_fixed_window = extract_charge(waveforms, self.height_peak, self.fixed_window)

return peak_time, charge_integral




@guvectorize(
[
(np.uint16[:], np.uint8, np.uint8, np.uint16[:]),
],
"(n,p,s),(),()->(n,p)",
nopython=True,
cache=True,
)

def extract_charge(y,height_peak,fixed_window) :
x = np.linspace(0, len(y), len(y))
xi = np.linspace(0, len(y), 251)
ius = InterpolatedUnivariateSpline(x, y)
yi = ius(xi)
peaks, _ = find_peaks(yi, height=height_peak)
# find the peak
if len(peaks)>0:
# find the max peak
# max_peak = max(yi[peaks])
max_peak_index = np.argmax(yi[peaks], axis=0)
# Check if there is not a peak but a plateaux
# 1. divide for the maximum to round to the first digit
# 2. create a new array with normalized and rounded values, yi_rounded
yi_rounded = np.around(yi[peaks]/max(yi[peaks]),1)
maxima_peak_index = np.argwhere(yi_rounded == np.amax(yi_rounded))
if len(maxima_peak_index)>1:
# saturated event
max_peak_index = int(np.median(maxima_peak_index))
# width_peak = 20
if (xi[peaks[max_peak_index]]>20)&(xi[peaks[max_peak_index]]<40): # Search the adaptive integration window
# calculate total gradients (not used, only for plot)
yi_grad_tot = np.gradient(yi, 1)
maxposition = peaks[max_peak_index]
# calcualte grandients starting from the max peak and going to the left to find the left margin of the window
yi_left = yi[:maxposition]
yi_grad_left = np.gradient(yi_left[::-1], 0.9)
change_grad_pos_left = (np.where(yi_grad_left[:-1] * yi_grad_left[1:] < 0 )[0] +1)[0]
# calcualte grandients starting from the max peak and going to the right to find the right margin of the window
yi_right = yi[maxposition:]
yi_grad_right = np.gradient(yi_right, 0.5)
change_grad_pos_right = (np.where(yi_grad_right[:-1] * yi_grad_right[1:] < 0 )[0] +1)[0]
charge_integral = ius.integral(xi[peaks[max_peak_index]-(change_grad_pos_left)], xi[peaks[max_peak_index]+ change_grad_pos_right])
charge_sum = yi[(peaks[max_peak_index]-(change_grad_pos_left)):peaks[max_peak_index]+change_grad_pos_right].sum()/(change_grad_pos_left+change_grad_pos_right) # simple sum integration
adaptive_window = change_grad_pos_right + change_grad_pos_left
window_right = (fixed_window-6)/2
window_left = (fixed_window-6)/2 + 6
charge_integral_fixed_window = ius.integral(xi[peaks[max_peak_index]-(window_left)], xi[peaks[max_peak_index]+window_right])
charge_sum_fixed_window = yi[(peaks[max_peak_index]-(window_left)):peaks[max_peak_index]+window_right].sum()/(fixed_window) # simple sum integration
else:
log.info('No peak found, maybe it is a pedestal or noisy run!')
return adaptive_window, charge_integral, charge_sum, charge_integral_fixed_window, charge_sum_fixed_window
177 changes: 177 additions & 0 deletions nectarchain/calibration/container/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
from email.generator import Generator
import os
import glob
import re
import mechanize
import requests
import browser_cookie3

from DIRAC.Interfaces.API.Dirac import Dirac
from pathlib import Path
from typing import List,Tuple


import logging
logging.basicConfig(format='%(asctime)s %(name)s %(levelname)s %(message)s')
log = logging.getLogger(__name__)
log.handlers = logging.getLogger('__main__').handlers

__all__ = ['DataManagment','ChainGenerator']

class DataManagment() :
@staticmethod
def findrun(run_number : int,search_on_GRID = True) -> Tuple[Path,List[Path]]:
"""method to find in NECTARCAMDATA the list of *.fits.fz files associated to run_number

Args:
run_number (int): the run number

Returns:
(PosixPath,list): the path list of *fits.fz files
"""
basepath=os.environ['NECTARCAMDATA']
list = glob.glob(basepath+'**/*'+str(run_number)+'*.fits.fz',recursive=True)
list_path = [Path(chemin) for chemin in list]
if len(list_path) == 0 :
e = FileNotFoundError(f"run {run_number} is not present in {basepath}")
if search_on_GRID :
log.warning(e,exc_info=True)
log.info('will search files on GRID and fetch them')
lfns = DataManagment.get_GRID_location(run_number)
DataManagment.getRunFromDIRAC(lfns)
list = glob.glob(basepath+'**/*'+str(run_number)+'*.fits.fz',recursive=True)
list_path = [Path(chemin) for chemin in list]
else :
log.error(e,exc_info=True)
raise e



name = list_path[0].name.split(".")
name[2] = "*"
name = Path(str(list_path[0].parent))/(f"{name[0]}.{name[1]}.{name[2]}.{name[3]}.{name[4]}")
log.info(f"Found {len(list_path)} files matching {name}")

#to sort list path
_sorted = sorted([[file,int(file.suffixes[1][1:])] for file in list_path])
list_path = [_sorted[i][0] for i in range(len(_sorted))]

return name,list_path

@staticmethod
def getRunFromDIRAC(lfns : list):
"""method do get run files from GRID-EGI from input lfns

Args:
lfns (list): list of lfns path
"""

dirac = Dirac()
for lfn in lfns :
if not(os.path.exists(f'{os.environ["NECTARCAMDATA"]}/{os.path.basename(lfn)}')):
dirac.getFile(lfn=lfn,destDir=os.environ["NECTARCAMDATA"],printOutput=True)


@staticmethod
def get_GRID_location(run_number : int,output_lfns = True, username = None,password = None) :
guillaumegrolleron marked this conversation as resolved.
Show resolved Hide resolved
"""method to get run location on GRID from Elog (work in progress!)

Args:
run_number (int): run number
output_lfns (bool, optional): if True, return lfns path of fits.gz files, else return parent directory of run location. Defaults to True.
username (_type_, optional): username for Elog login. Defaults to None.
password (_type_, optional): password for Elog login. Defaults to None.

Returns:
_type_: _description_
"""

url = "http://nectarcam.in2p3.fr/elog/nectarcam-data-qm/?cmd=Find"

url_run = f"http://nectarcam.in2p3.fr/elog/nectarcam-data-qm/?mode=full&reverse=0&reverse=1&npp=20&subtext=%23{run_number}"

#try to acces data by getting cookies from firefox and Chrome
log.debug('try to get data with cookies from Firefox abnd Chrome')
cookies = browser_cookie3.load()
req = requests.get(f'http://nectarcam.in2p3.fr/elog/nectarcam-data-qm/?jcmd=&mode=Raw&attach=1&printable=1&reverse=0&reverse=1&npp=20&ma=&da=&ya=&ha=&na=&ca=&last=&mb=&db=&yb=&hb=&nb=&cb=&Author=&Setup=&Category=&Keyword=&Subject=&ModuleCount=&subtext=%23{run_number}',cookies = cookies)

if "<title>ELOG Login</title>" in req.text :
log.debug('log to Elog with cookies impossible, try again with password')
#log to Elog
br = mechanize.Browser()
br.open(url)

form = br.select_form('form1')

for i in range(4) :
log.debug(br.form.find_control(nr=i).name)

br.form['uname'] = username
br.form['upassword'] = password
br.method = "POST"

req = br.submit()
#html_page = req.get_data()
cookies = br._ua_handlers['_cookies'].cookiejar

#get data
req = requests.get(f'http://nectarcam.in2p3.fr/elog/nectarcam-data-qm/?jcmd=&mode=Raw&attach=1&printable=1&reverse=0&reverse=1&npp=20&ma=&da=&ya=&ha=&na=&ca=&last=&mb=&db=&yb=&hb=&nb=&cb=&Author=&Setup=&Category=&Keyword=&Subject=&ModuleCount=&subtext=%23{run_number}',cookies = cookies)


lines = req.text.split('\r\n')

url_data = None
for line in lines :
if '<p>' in line :
url_data = line.split("</p>")[0].split('FC:')[1]
break

if output_lfns :
try :
#Dirac
dirac = Dirac()
loc = f"/vo.cta.in2p3.fr/nectarcam/{url_data.split('/')[-2]}/{url_data.split('/')[-1]}"
res = dirac.listCatalogDirectory(loc, printOutput=True)

lfns = []
for key in res['Value']['Successful'][loc]['Files'].keys():
if str(run_number) in key and "fits.fz" in key :
lfns.append(key)
except Exception as e :
log.error(e,exc_info = True)
return lfns
else :
return url_data


class ChainGenerator():
@staticmethod
def chain(a : Generator ,b : Generator) :
"""generic metghod to chain 2 generators

Args:
a (Generator): generator to chain
b (Generator): generator to chain

Yields:
Generator: a chain of a and b
"""
yield from a
yield from b


@staticmethod
def chainEventSource(list : list,max_events : int = None) : #useless with ctapipe_io_nectarcam.NectarCAMEventSource
"""recursive method to chain a list of ctapipe.io.EventSource (which may be associated to the list of *.fits.fz file of one run)

Args:
list (EventSource): a list of EventSource

Returns:
Generator: a generator which chains EventSource
"""
if len(list) == 2 :
return ChainGenerator.chain(list[0],list[1])
else :
return ChainGenerator.chain(list[0],ChainGenerator.chainEventSource(list[1:]))

Loading