In [1]:
import os, math
from dataclasses import dataclass
import numpy as np
import pandas as pd
from numba import vectorize, float64, boolean, njit
from scipy.signal import find_peaks
import matplotlib.pyplot as plt
import holoviews as hv
from IPython.display import Markdown
from holoviews.operation import decimate
from holoviews.operation.datashader import datashade,rasterize, dynspread
import dask.dataframe as dd
# import hvplot.pandas
# import hvplot.dask

hv.extension('bokeh', logo = False)
In [2]:
# turn off numpy warning for overflow in np.exp()
np.seterr(over='ignore');
In [3]:
shot_no = 0

(Internal Tektronix format) .wfm parser¶

In [4]:
# wfm reader proof-of-concept
# https://www.tek.com/sample-license
# reads volts vs. time records (including fastframes) from little-endian version 3 WFM files

# See Also
# Performance Oscilloscope Reference Waveform File Format
# Tektronix part # 077-0220-10
# https://www.tek.com/oscilloscope/dpo7000-digital-phosphor-oscilloscope-manual-4

import struct
import time
import numpy as np # http://www.numpy.org/

class WfmReadError(Exception):
    """error for unexpected things"""
    pass

def read_wfm(target):
    """return sample data from target WFM file"""
    with open(target, 'rb') as f:
        hbytes = f.read(838)
        meta = decode_header(hbytes)
        # file signature checks
        if meta['byte_order'] != 0x0f0f:
            raise WfmReadError('big-endian not supported in this example')
        if meta['version'] != b':WFM#003':
            raise WfmReadError('only version 3 wfms supported in this example')
        if meta['imp_dim_count'] != 1:
            raise WfmReadError('imp dim count not 1')
        if meta['exp_dim_count'] != 1:
            raise WfmReadError('exp dim count not 1')
        if meta['record_type'] != 2:
            raise WfmReadError('not WFMDATA_VECTOR')
        if meta['exp_dim_1_type'] != 0:
            raise WfmReadError('not EXPLICIT_SAMPLE')
        if meta['time_base_1'] != 0:
            raise WfmReadError('not BASE_TIME')
        tfrac_array = np.zeros(meta['Frames'], dtype=np.double)
        tdatefrac_array = np.zeros(meta['Frames'], dtype=np.double)
        tdate_array = np.zeros(meta['Frames'], dtype=np.int32)
        tfrac_array[0] = meta['tfrac']
        tdatefrac_array[0] = meta['tdatefrac']
        tdate_array[0] = meta['tdate']
        # if fastframe, read fastframe table
        if meta['fastframe'] == 1:
            WUSp = np.fromfile(f, dtype='i4,f8,f8,i4', count=(meta['Frames'] - 1))
            # merge first frame trigger infos with frames > 1
            tfrac_array[1:] = WUSp['f1']
            tdatefrac_array[1:] = WUSp['f2']
            tdate_array[1:] = WUSp['f3']
        # read curve block
        bin_wave = np.memmap(filename = f,
                             dtype = meta['dformat'],
                             mode = 'r',
                             offset = meta['curve_offset'],
                             shape = (meta['avilable_values'], meta['Frames']),
                             order = 'F')
        # close file
    # slice out buffer values
    bin_wave = bin_wave[meta['pre_values']:meta['avilable_values'] - meta['post_values'],:]
    scaled_array = bin_wave * meta['vscale'] + meta['voffset']
    return scaled_array, meta['tstart'], meta['tscale'], tfrac_array, tdatefrac_array, tdate_array

def decode_header(header_bytes):
    """returns a dict of wfm metadata"""
    wfm_info = {}
    if len(header_bytes) != 838:
        raise WfmReadError('wfm header bytes not 838')
    wfm_info['byte_order'] = struct.unpack_from('H', header_bytes, offset=0)[0]
    wfm_info['version'] = struct.unpack_from('8s', header_bytes, offset=2)[0]
    wfm_info['imp_dim_count'] = struct.unpack_from('I', header_bytes, offset=114)[0]
    wfm_info['exp_dim_count'] = struct.unpack_from('I', header_bytes, offset=118)[0]
    wfm_info['record_type'] = struct.unpack_from('I', header_bytes, offset=122)[0]
    wfm_info['exp_dim_1_type'] = struct.unpack_from('I', header_bytes, offset=244)[0]
    wfm_info['time_base_1'] = struct.unpack_from('I', header_bytes, offset=768)[0]
    wfm_info['fastframe'] = struct.unpack_from('I', header_bytes, offset=78)[0]
    wfm_info['Frames'] = struct.unpack_from('I', header_bytes, offset=72)[0] + 1
    wfm_info['summary_frame'] = struct.unpack_from('h', header_bytes, offset=154)[0]
    wfm_info['curve_offset'] = struct.unpack_from('i', header_bytes, offset=16)[0] # 838 + ((frames - 1) * 54)
    # scaling factors
    wfm_info['vscale'] = struct.unpack_from('d', header_bytes, offset=168)[0]
    wfm_info['voffset'] = struct.unpack_from('d', header_bytes, offset=176)[0]
    wfm_info['tstart'] = struct.unpack_from('d', header_bytes, offset=496)[0]
    wfm_info['tscale'] = struct.unpack_from('d', header_bytes, offset=488)[0]
    # trigger detail
    wfm_info['tfrac'] = struct.unpack_from('d', header_bytes, offset=788)[0] # frame index 0
    wfm_info['tdatefrac'] = struct.unpack_from('d', header_bytes, offset=796)[0] # frame index 0
    wfm_info['tdate'] = struct.unpack_from('I', header_bytes, offset=804)[0] # frame index 0
    # data offsets
    # frames are same size, only first frame offsets are used
    dpre = struct.unpack_from('I', header_bytes, offset=822)[0]
    wfm_info['dpre'] = dpre
    dpost = struct.unpack_from('I', header_bytes, offset=826)[0]
    wfm_info['dpost'] = dpost
    readbytes = dpost - dpre
    wfm_info['readbytes'] = readbytes
    allbytes = struct.unpack_from('I', header_bytes, offset=830)[0]
    wfm_info['allbytes'] = allbytes
    # sample data type detection
    code = struct.unpack_from('i', header_bytes, offset=240)[0]
    wfm_info['code'] = code
    bps = struct.unpack_from('b', header_bytes, offset=15)[0]  # bytes-per-sample
    wfm_info['bps'] = bps
    if code == 7 and bps == 1:
        dformat = 'int8'
        samples = readbytes
    elif code == 0 and bps == 2:
        dformat = 'int16'
        samples = readbytes // 2
    elif code == 4 and bps == 4:
        dformat = 'single'
        samples = readbytes // 4
    else:
        raise WfmReadError('data type code or bytes-per-sample not understood')
    wfm_info['dformat'] = dformat
    wfm_info['samples'] = samples
    wfm_info['avilable_values'] = allbytes // bps
    wfm_info['pre_values'] = dpre // bps
    wfm_info['post_values'] = (allbytes - dpost) // bps
    return wfm_info

def readwfm(path):
    volts, tstart, tscale, tfrac, tdatefrac, tdate = read_wfm(path)
    toff = tfrac * tscale
    samples, frames = volts.shape
    tstop = samples * tscale + tstart
    volts = volts.reshape(len(volts))
    time = np.linspace(tstart+toff, tstop+toff, num=samples, endpoint=False)
    time = time.reshape(len(volts))
    
    return time,volts

Get Hi-Res data from MSO58 oscilloscope¶

In [5]:
# load data locally
fnames = ['DAS_raw_data_dir/ch2.wfm',
          'DAS_raw_data_dir/ch3.wfm',
          'DAS_raw_data_dir/ch4.wfm',
          'DAS_raw_data_dir/ch5.wfm',
          'DAS_raw_data_dir/ch6.wfm',
          'DAS_raw_data_dir/ch7.wfm']
ds = np.DataSource('/tmp/')
if not os.path.isfile('DAS_raw_data_dir/ch6.wfm'):
        # or download from web (slow)
        for i, ch_no in enumerate(range(2,8)):
            try:
                file = ds.open(f'http://golem.fjfi.cvut.cz/shots/{shot_no}/Devices/Oscilloscopes/TektrMSO58-a/ch{ch_no}.wfm', mode = 'rb')
                fnames[i] = file.name
                file.close()
            except (ConnectionError, FileNotFoundError):
                err = Markdown('### Can not download data')
        
t_vec, y_ch2 = readwfm(fnames[0])
_, y_ch3 = readwfm(fnames[1])
_, y_ch4 = readwfm(fnames[2])
_, y_ch5 = readwfm(fnames[3])
_, y_ch6 = readwfm(fnames[4])
_, y_ch7 = readwfm(fnames[5])

Check if data has sufficient sample rate

In [6]:
dt = t_vec[1]-t_vec[0]
MSamples =  1e-6/(dt) 

downsample = int(np.rint(float(1e-6) / dt))

if MSamples >= 120:
    out = Markdown(f'### Data with {MSamples:.1f}MS/s resolution') 
    low_res_data = False
else:
    out = Markdown('### Low resolution for peak recononstruction \n adjust oscilloscope settings')
    low_res_data = True
out
Out[6]:

Data with 250.0MS/s resolution¶

In [7]:
# class for storing channels and their parameters and results

@dataclass
class channel:
    name : str
    time : np.ndarray
    y :  np.ndarray
    noPlot : bool = False
    noFit : bool = False

    ## parameters for peak detection
    mean_noise_threshold_mult : float = 1.75
    peak_prominence_thr : float = 0.001

    # peak rise / fall time
    rise_time : float = None
    fall_time : float = None
    
    # calibration
    calibration : float = None  # keV/V
    
    # Flip waveform of PMTs but no SiPMs
    flip_waveform : bool = True
    
    ## parameters recieved from further analysis
    mean_channel_noise : float = 0
    
    # threshold
    threshold : float = 0
    
    # arrays for results
    simple_peak_loc : np.ndarray = None
    lonely_peak_loc : np.ndarray = None
    
    # for debug
    lonely_peak_mask : np.ndarray = None
    
    no_corr_peak_height : np.ndarray = None
    lonely_peak_height : np.ndarray = None
    simple_corr_peak_height : np.ndarray = None
    simple_corr_peak_err : np.ndarray = None

    simple_corr_peak_filtered_loc : np.ndarray = None
    simple_corr_peak_filtered_height : np.ndarray = None
    
    
    multipass_peak_loc : np.ndarray = None
    multipass_corr_peak_height : np.ndarray = None
    multipass_corr_peak_err : np.ndarray = None

    multipass_corr_peak_filtered_loc : np.ndarray = None
    multipass_corr_peak_filtered_height : np.ndarray = None
    
    y_diff : np.ndarray = None
    y_gen :  np.ndarray = None
    y_gen2 :  np.ndarray = None
    
    @property
    def out_fname(self):
        return f'{ch.name}_{shot_no}'
In [8]:
# set channel and their parameters
ch2 = channel('YaP',     t_vec, y_ch2, noFit=False)  # @ HV:600 V
ch3 = channel('CeBr-a',  t_vec, y_ch3, rise_time=1.457e-09, fall_time=2.754e-08, calibration=12578.)  # @ HV:600 V
ch4 = channel('CeBr-b',  t_vec, y_ch4, rise_time=3.410e-09, fall_time=2.786e-08, calibration=6714., noFit= True)   # @ HV:600 V
ch5 = channel('NaITl',   t_vec, y_ch5, rise_time=2.258e-08, fall_time=2.202e-07, calibration=71799., noFit = True)  # @ HV:600 V
ch6 = channel('LYSO-1',  t_vec, y_ch6, rise_time=5.946e-09, fall_time=5.146e-08, calibration=5007., flip_waveform=False) # @ C:300mV
ch7 = channel('GaGG',    t_vec, y_ch7, flip_waveform=False, noFit=True)


channels = [ch3,ch4,ch5,ch6,ch7,ch2]

Show raw data¶

Note: SiPM has range from 0V to +1V
anything above is clipped

In [9]:
color_cycle = plt.rcParams['axes.prop_cycle'].by_key()['color']
fig, axes = plt.subplots(len(channels), sharex  =True)
[ax.plot(ch.time[::downsample],ch.y[::downsample], 
         label = ch.name, color = c) for ax, ch, c in zip(axes, channels, color_cycle)]
[ax.axhline(1., color = 'red', ls = '--') 
    for ax, ch in zip(axes, channels) if not ch.flip_waveform and np.any(ch.y > 1.)]

fig.legend()

is_clipping = [np.any(ch.y > 1.) for ch in channels if not ch.flip_waveform]
if np.any(is_clipping):
    warning = Markdown("""### SiPM's integrated amplifier saturated
    HXR spectrum reconstruction from whole discharge may be unreliable
    """)
    display(warning)
No description has been provided for this image

Remove offset from OSC waveforms and flip¶

In [10]:
def get_noise_level(t,y_vals,noise_time):
    y_noise = y_vals[t < noise_time]
    y_noise = np.nan_to_num(y_noise, posinf = 0, neginf = 0)

    mean_y = np.mean(y_noise)
    
    # mean noise amplitude
    abs_noise = np.abs(y_noise - mean_y)**2
    mean_noise_ampl = np.sqrt(np.mean(abs_noise))

    return mean_y, mean_noise_ampl

def moving_average(x, w):
    return np.convolve(x, np.ones(w), 'save') / w


for ch in channels:
    noise, ch.mean_channel_noise = get_noise_level(ch.time, ch.y, 0.000)
    ch.y = ch.y - noise
    ch.threshold = ch.mean_channel_noise*ch.mean_noise_threshold_mult
    
    if ch.flip_waveform:
        ch.y *=-1
    
    ## smooth-out noise
    ch.y = moving_average(ch.y, 5)

Plot OSC waveforms and peak thresholds¶

In [11]:
#%matplotlib widget
fig, axes = plt.subplots(len(channels), sharex  =True)
for ax,ch,color in zip(axes,channels, color_cycle):
    ax.plot(ch.time[::downsample]*1e3, ch.y[::downsample], label = ch.name, color=color)
    ax.axhline(ch.mean_channel_noise,ls= '-.', c = 'k')
    ax.axhline(ch.threshold,ls= '--', c = 'k')
    ax.set_ylim(-2*ch.threshold, 2*ch.threshold)
    ax.grid()

fig.legend();
No description has been provided for this image

Find peaks¶

In [12]:
max_HXR_energy = 10.

for ch in channels:
    peaks_loc, _ = find_peaks(ch.y, height=ch.threshold, prominence = ch.mean_channel_noise*2)
    ch.simple_peak_loc = peaks_loc
    ch.no_corr_peak_height = ch.y[peaks_loc]
    
    if ch.calibration is not None and not ch.noPlot and not ch.noFit:
        if ch.name == 'NaITl':
            continue
        max_HXR_energy = np.max((max_HXR_energy, ch.no_corr_peak_height.max()*ch.calibration))

Define useful functions¶

In [13]:
@vectorize(['float64(float64, boolean)'])
def half_exp(x, where):
    '''compute exp function only for selected indices (to avoid infinite/large values)'''
    if where:
        return np.exp(x)
    else:
        return 0.0

def get_index(oneDArray, val):
    diff = np.abs(oneDArray - val)
    return np.argmin(diff)

def exp_decay(t, height, center, rise, fall):
    '''smooth peak fucntion from https://arxiv.org/pdf/1706.01211.pdf eq:(2)'''
    # bool mask where compute exp functions (without it results in +-inf values)
    exp_window_split_index = get_index(t, center-100*rise)
    exp_window = np.zeros_like(t, np.bool_)
    exp_window[exp_window_split_index:].fill(True)
    
    # scaling and shifting of peak fucntion
    A = height / ( (fall-rise)/fall * (fall/rise - 1 )**(-rise/fall)  )
    peak_center_shift = rise*np.log((fall-rise)/rise)
    center = center - peak_center_shift
    
    
    peak = A * 1/(1+half_exp(-(t-center)/rise, exp_window)) * half_exp(-(t-center)/fall, exp_window)
    return peak

def gen_peaks(time, HXR_y, peaks_loc, channel_props, test_diff: bool = True):
    
    corrected_peak_heights = np.zeros_like(peaks_loc, float)
    HXR_generated = np.zeros_like(HXR_y)
    diff = HXR_y.copy()
    discarded_peaks = np.zeros_like(peaks_loc, np.bool_)
    
    # operate only on small slice of waveform (it's faster)
    dt = np.mean(np.diff(time))
    i_slice = max(int(channel_props.fall_time*100 / dt),1000)
        
    
    for n, peak in enumerate(peaks_loc):
        # use differece between generated and original waveform to obtain peak height
        peak_height = diff[peak]
        peak_center = time[peak]
        
        slice_idx_left = max(peak-i_slice,0)
        slice_idx_right = min(peak+i_slice,len(HXR_y)-1)  
        gen_slice_time = time[slice_idx_left:slice_idx_right]

        if peak_height < 0.:
            corrected_peak_heights[n] = 0.
            continue
        
        # generate peak
        generated_peak = exp_decay(gen_slice_time, peak_height, peak_center, 
                                   channel_props.rise_time, channel_props.fall_time)
        
        # if adding this peak increases differance btw original and generated signal
        # discard this peak as false detection
        diff_test = diff[slice_idx_left:slice_idx_right].copy()
        diff_test -= generated_peak
        if test_diff and np.sum(np.abs(diff_test)) > np.sum(np.abs(diff[slice_idx_left:slice_idx_right])):
            corrected_peak_heights[n] = 0.
            discarded_peaks[n] = True
        else:
            # subtract peak from signal and record peak height
            diff[slice_idx_left:slice_idx_right]  -= generated_peak
            HXR_generated[slice_idx_left:slice_idx_right] += generated_peak
        
            corrected_peak_heights[n] = peak_height
        
    return corrected_peak_heights, HXR_generated, diff, discarded_peaks

@njit
def is_lonely_peak(time, HXR_y, peaks, peak_no , fall_time, mean_noise_level):
    peak_fall_time =  fall_time * np.log(HXR_y[peaks[peak_no]]/mean_noise_level)
    
    if peak_no != 0:
        t_before = time[peaks[peak_no]] - peak_fall_time
        t_peak_before = time[peaks[peak_no-1]]
        if  t_before < t_peak_before:
            return False

    if peak_no <= peaks.size-2:
        t_after  = time[peaks[peak_no]] + peak_fall_time
        t_peak_after = time[peaks[peak_no+1]]
        if t_peak_after < t_after:
            return False
    return True

def identify_lonely_peaks(time,HXR_y, peaks, channel_props):
    def is_lonely_peak_reduced(n):
        return is_lonely_peak(time,
                              HXR_y,
                              peaks, 
                              n, 
                              channel_props.fall_time, 
                              channel_props.mean_channel_noise)
    
    lonely_peaks_idx = [is_lonely_peak_reduced(n) for n in  range(peaks.size)]
    lonely_peaks_idx = np.array(lonely_peaks_idx, np.bool_)
    return lonely_peaks_idx

def find_nearest_idx(array,value):
    idx = np.searchsorted(array, value, side="left")
    if idx > 0 and (idx == len(array) or math.fabs(value - array[idx-1]) < math.fabs(value - array[idx])):
        return idx-1
    else:
        return idx

def estimate_peak_err(time, HXR_y, diff, peak_loc, channel_props):
    fall_time = channel_props.fall_time
    mean_noise_level = channel_props.mean_channel_noise
    peak_fall_times =  fall_time * np.log(HXR_y[peak_loc]/mean_noise_level)

    # get maximum of diff in [peak-peak_fall_time ; peak+peak_fall_time]
    left_index = (find_nearest_idx(time, time[p]-peak_fall_time) for p,peak_fall_time in zip(peak_loc,peak_fall_times))
    right_index = (find_nearest_idx(time, time[p]+peak_fall_time) for p, peak_fall_time in zip(peak_loc,peak_fall_times))

    # how to vectorize?
    peak_error_estimate = np.array([np.max(np.abs(diff[l:r])) for l,r in zip(left_index,right_index)])
    return peak_error_estimate
In [14]:
for ch in channels:
    if ch.noFit or ch.rise_time is None or ch.rise_time is None:
        continue
    
    ch.simple_corr_peak_height, ch.y_gen, ch.y_diff ,_ =  gen_peaks(ch.time, ch.y,
                                                                  ch.simple_peak_loc, 
                                                                  ch)
    ch.simple_corr_peak_err = estimate_peak_err(ch.time, ch.y, ch.y_diff, ch.simple_peak_loc, ch)
    
    ch.lonely_peak_mask = identify_lonely_peaks(ch.time, ch.y,
                                                ch.simple_peak_loc,
                                                ch)
    
    ch.lonely_peak_loc = ch.simple_peak_loc[ch.lonely_peak_mask].copy()
    ch.lonely_peak_height = ch.no_corr_peak_height[ch.lonely_peak_mask].copy()

    relative_error = np.zeros_like(ch.simple_corr_peak_height)
    np.divide(ch.simple_corr_peak_err, ch.simple_corr_peak_height, out=relative_error, where=ch.simple_corr_peak_height != 0)
    
    filtering_mask = relative_error < 0.1
    ch.simple_corr_peak_filtered_loc = ch.simple_peak_loc[filtering_mask]
    ch.simple_corr_peak_filtered_height = ch.simple_corr_peak_height[filtering_mask]
In [15]:
# plots = list()
# for ch in channels:
#     if ch.noFit or ch.rise_time is None or ch.rise_time is None or ch.noPlot:
#         continue
    
#     l_gen = '%s gen' % ch.name
#     l_dif = '%s diff' % ch.name
#     data_to_plot = {ch.name : ch.y, l_gen : ch.y_gen, l_dif : ch.y_diff}
#     df = pd.DataFrame(data_to_plot,index = pd.Index(ch.time*1e3, name = 'time'))
#     ddf = dd.from_pandas(df, npartitions=os.cpu_count())
    
#     gen_pl = ddf[l_gen].hvplot.line(rasterize = True, cmap=['green'], colorbar=False)
#     org_pl = ddf[ch.name].hvplot.line(rasterize = True, cmap=['blue'], colorbar=False)
#     diff_pl = ddf[l_dif].hvplot.line(rasterize = True, cmap=['red'], colorbar=False)
#     peaks_pl = hv.ErrorBars((ch.time[ch.simple_peak_loc]*1e3,ch.y[ch.simple_peak_loc],ch.simple_corr_peak_height,0),
#                             vdims=['U_%s' % ch.name, 'yerrneg', 'yerrpos'],label = 'peaks').opts(upper_head=None, lower_head=None)
    
#     plot = org_pl * gen_pl * diff_pl * peaks_pl
#     plot.opts(width=950, height = 200).relabel(ch.name)
#     plots.append(plot)

# hv.Layout(plots).cols(1)
In [16]:
fig,(ax,ax2,ax3) = plt.subplots(3, sharex = True, sharey=True)
for ch, color in zip(channels, color_cycle):
    ax.hist(ch.no_corr_peak_height, bins = 100, label = '%s' % ch.name, histtype = 'step', color = color)
    if ch.noFit or ch.rise_time is None or ch.fall_time is None:
        continue
    ax2.hist(ch.simple_corr_peak_height[ch.lonely_peak_mask], bins = 100, histtype = 'step', color = color)
    ax3.hist(ch.simple_corr_peak_filtered_height, bins = 100, histtype = 'step', color = color)
    
ax.grid()
ax2.grid()
ax3.grid()

ax.semilogy()
ax2.semilogy()
ax3.semilogy()

fig.legend();
No description has been provided for this image

Use differance to find less prominent peaks¶

In [17]:
for ch in channels:
    if ch.noFit or  ch.rise_time is None or ch.rise_time is None:
        continue
    
    peaks_loc = ch.simple_peak_loc
    for pass_no in range(3):
        peaks_locNew, _ = find_peaks(ch.y_diff, height=ch.threshold, prominence = ch.mean_channel_noise*2)
        if peaks_locNew.size == 0:
            continue
        
        _, _, ch.y_diff,_ =  gen_peaks(ch.time, 
                                       ch.y_diff, 
                                       peaks_locNew, 
                                       ch,
                                       test_diff = False)

        peaks_loc = np.concatenate((peaks_loc,peaks_locNew))
        
    peaks_loc = np.sort(peaks_loc)
    ch.multipass_peak_loc = peaks_loc

Re-generate waveform¶

In [18]:
plots = list()

for ch in channels:
    if ch.noFit or ch.rise_time is None or ch.rise_time is None:
        continue
    peaks_loc = ch.multipass_peak_loc
    
    corrected_peak_heights, ch.y_gen2, ch.y_diff, discarded =  gen_peaks(ch.time, 
                                                                      ch.y, 
                                                                      peaks_loc,
                                                                      ch,
                                                                      test_diff = True)
    
    ### Discard false positives, peaks below threshold and saturated values
    saturated = ch.y[peaks_loc]>0.9
    mask_discarded = np.logical_or(discarded, corrected_peak_heights<ch.threshold, saturated)
    corrected_peak_heights = corrected_peak_heights[~mask_discarded]
    peaks_loc = peaks_loc[~mask_discarded]
    
    ch.multipass_peak_loc = peaks_loc
    ch.multipass_corr_peak_height = corrected_peak_heights

    ch.multipass_corr_peak_err = estimate_peak_err(ch.time, ch.y, ch.y_diff, ch.multipass_peak_loc, ch)

    relative_error = np.zeros_like(ch.multipass_corr_peak_height)
    np.divide(ch.multipass_corr_peak_err, ch.multipass_corr_peak_height, out=relative_error, where=ch.multipass_corr_peak_height != 0)
    filtering_mask = relative_error < 0.1
    ch.multipass_corr_peak_filtered_loc = ch.multipass_peak_loc[filtering_mask]
    ch.multipass_corr_peak_filtered_height = ch.multipass_corr_peak_height[filtering_mask]
In [19]:
# plots = list()
# for ch in channels:
#     if ch.noFit or ch.rise_time is None or ch.rise_time is None or ch.noPlot:
#         continue
    
#     l_gen = '%s gen' % ch.name
#     l_gen2 = '%s gen2' % ch.name
#     l_dif = '%s diff' % ch.name
#     data_to_plot = {ch.name : ch.y, l_gen : ch.y_gen, l_dif : ch.y_diff, l_gen2 : ch.y_gen2}
#     df = pd.DataFrame(data_to_plot, index = pd.Index(ch.time*1e3, name = 'time'))
#     ddf = dd.from_pandas(df, npartitions=os.cpu_count())

#     gen_pl = ddf[l_gen].hvplot.line(rasterize = True, cmap=['olive'], colorbar=False)
#     gen_pl2 = ddf[l_gen2].hvplot.line(rasterize = True, cmap=['green'], colorbar=False)
#     org_pl = ddf[ch.name].hvplot.line(rasterize = True, cmap=['blue'], colorbar=False)
#     diff_pl = ddf[l_dif].hvplot.line(rasterize = True, cmap=['red'], colorbar=False)
    
#     peaks_pl = hv.ErrorBars((ch.time[ch.multipass_peak_loc]*1e3,ch.y[ch.multipass_peak_loc],ch.multipass_corr_peak_height,0),
#                             vdims=['U_%s' % ch.name, 'yerrneg', 'yerrpos']).opts(upper_head=None, lower_head=None)
    
#     plot = org_pl * gen_pl * gen_pl2 * diff_pl * peaks_pl
#     plot.opts(width=950, height = 200).relabel(ch.name)
#     plots.append(plot)

# plot = hv.Layout(plots).cols(1)
# plot

Show HXR energy spectrum¶

In [20]:
fig, ax = plt.subplots()
for ch,color in zip(channels,color_cycle):
    if ch.noPlot or ch.calibration is None:
        continue

    _, bins, _ = ax.hist(ch.no_corr_peak_height*ch.calibration, bins = 100, label = ch.name,
                         range = (0,max_HXR_energy), histtype = 'step', ls = '--', color = color)
    
    if ch.noFit or ch.rise_time is None or ch.rise_time is None:
        continue
    
    h1, b1 = np.histogram(ch.simple_corr_peak_filtered_height*ch.calibration, bins = len(bins)-1, range = (bins[0], bins[-1]))
    h2, b2 = np.histogram(ch.multipass_corr_peak_filtered_height*ch.calibration, bins = len(bins)-1, range = (bins[0], bins[-1]))
    h = (h1+h2)/2
    h_p = np.maximum(h1,h2)-h
    h_m = h-np.minimum(h1,h2)
    
    ax.errorbar(bins[:-1], h, yerr = (h_m, h_p), color = color, ls = '')

    

fig.legend(frameon =True, fancybox=True,framealpha=1.)
ax.semilogy()
ax.grid()
ax.set_xlabel('$E$ [keV]')
ax.set_ylabel('N [-]')
Out[20]:
Text(0, 0.5, 'N [-]')
No description has been provided for this image
In [21]:
fig,(ax,ax2,ax3,ax4) = plt.subplots(4, sharex = True, sharey=True)
for ch, color in zip(channels, color_cycle):
    if ch.noFit or ch.rise_time is None or ch.fall_time is None:
        continue
    ax.hist(ch.simple_corr_peak_height*ch.calibration, bins = 100, label = '%s' % ch.name, histtype = 'step', color = color)
    ax2.hist(ch.simple_corr_peak_filtered_height*ch.calibration, bins = 100, histtype = 'step', color = color)
    ax3.hist(ch.multipass_corr_peak_height*ch.calibration, bins = 100, histtype = 'step', color = color)
    ax4.hist(ch.multipass_corr_peak_filtered_height*ch.calibration, bins = 100, histtype = 'step', color = color)
    
ax.grid()
ax2.grid()
ax3.grid()
ax4.grid()

ax.semilogy()
ax2.semilogy()
ax3.semilogy()
ax4.semilogy()


fig.legend();
No description has been provided for this image
In [22]:
ip_url = f'http://golem.fjfi.cvut.cz/shots/{shot_no}/Diagnostics/BasicDiagnostics/Results/Ip.csv'
ip = ds.open(ip_url)
ip = np.loadtxt(ip,delimiter=',')
In [23]:
plot_channels = []

for ch,color in zip(channels,color_cycle):
    if ch.noPlot or ch.calibration is None or ch.noFit:
        continue
    plot_channels.append(ch)

fig, axes = plt.subplots(nrows = len(plot_channels), ncols =2, figsize =  [1.5*6.4, 1.75*4.8])

prevAx = None
prevrAx = None
for ch,color,ax, rax in zip(plot_channels,color_cycle,axes[:,0],axes[:,1]):
    if ch.noPlot or ch.calibration is None:
        continue

    _, bins, _ = ax.hist(ch.no_corr_peak_height*ch.calibration, bins = 100, label = ch.name,
                         range = (0,max_HXR_energy), histtype = 'step', ls = '--', color = color)
    
    if ch.noFit or ch.rise_time is None or ch.rise_time is None:
        continue
    
    h1, b1 = np.histogram(ch.simple_corr_peak_height*ch.calibration, 
                          bins = len(bins)-1, range = (bins[0], bins[-1]))
    h2, b2 = np.histogram(ch.multipass_corr_peak_height*ch.calibration, 
                          bins = len(bins)-1, range = (bins[0], bins[-1]))
    h = (h1+h2)/2
    h_p = np.maximum(h1,h2)-h
    h_m = h-np.minimum(h1,h2)
  
    ax.errorbar(bins[:-1], h, yerr = (h_m, h_p),ds = 'steps', color = color)
    
    h3, b3 = np.histogram(ch.simple_corr_peak_height[ch.lonely_peak_mask]*ch.calibration, 
                          bins = len(bins)-1, range = (bins[0], bins[-1]))
    
    ax.step(bins[:-1], h3, color = color, ls = '--', alpha = .75)

    ip_l = rax.plot(ip[:,0],ip[:,1],'k--', label = '$I_p$')
    
    rax.grid(True,axis='x')
    rax = rax.twinx()
    
    peak_time = ch.time[ch.simple_peak_loc] * 1e3
    peak_height = ch.simple_corr_peak_height*ch.calibration
    rax.scatter(peak_time,peak_height, color = color,s = .1 )

    rax.grid(True)
    #rax.yaxis.tick_right()
    
    if not ch.flip_waveform:
        max_reliableE = ch.calibration * .9
        if np.any(ch.y[ch.simple_peak_loc]>max_reliableE):
            rax.axhline(max_reliableE/2,color = 'r',ls = '-')
    

    ax.semilogy()
    ax.grid()
    ax.set_ylabel('N [-]')    
    
    if prevAx is not None:
        ax.sharex(prevAx)

    prevAx = ax
    
    rax.yaxis.set_label_position("right")
    rax.set_ylabel('$E$ [keV]')
    
    if prevrAx is not None:
        #rax.sharex(prevrAx)
        rax.sharey(prevrAx)

    prevrAx = rax


newHandles = [ip_l[0]]
for ax in axes[:,0]:
    handles, labels = ax.get_legend_handles_labels()
    newHandles += handles

fig.legend(handles= newHandles,frameon =True, fancybox=True,framealpha=1.,loc = 'lower center')

ax.set_xlabel('$E$ [keV]')
rax.set_xlabel('t [ms]');
rax.set_ylim(-100,max_HXR_energy)

fig.suptitle(f"#{shot_no}");
plt.savefig('icon-fig.png')
No description has been provided for this image