Source code for fiducia.visualization

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Fri Mar  8 10:49:17 2019

Utilities for visualizing DANTE data.

@author: Pawel M. Kozlowski
"""

# python modules
import numpy as np
import matplotlib.pyplot as plt

# custom modules
import fiducia.pltDefaults


# listing all functions declared in this file so that sphinx-automodapi
# correctly documents them and doesn't document imported functions.
__all__ = ["plotResponse",
           "plotTraces",
           "plotStreak",
           "signalImg"]


[docs]def plotResponse(channels, responseFrame, knots, solid=True, title='Dante Response Functions'): r""" Plots response function curves with knot locations identified as vertical dashed lines. channels: list, numpy.ndarray List or array of relevant channels responseFrame: pandas.core.frame.DataFrame Pandas dataFrame containing response functions for each DANTE channel. See loadResponses(). knots: list, numpy.ndarray List or array of knot point photon energy value. See knotFind(). solid: Bool Includes solid angle in response function value if true. Necessary for plotting responses with correct units. Parameters ---------- Returns ------- Notes ----- See also -------- Examples -------- """ # getting sensible figure bounds yMax = np.max(np.max(responseFrame[channels])) yMin = yMax / 1e11 xMin = 1e1 xMax = 1e5 # plotting figure fig = plt.figure() for idx, channel in enumerate(channels): plt.loglog(responseFrame['Energy(eV)'], responseFrame[channel], label=str(channel)) for idx, _ in enumerate(knots): plt.loglog((knots[idx], knots[idx]), (yMin, yMax), '--', color='grey') plt.xlim((xMin, xMax)) plt.ylim((yMin, yMax)) plt.legend(frameon=False, labelspacing=0.001, borderaxespad=0.1) plt.xlabel('Energy (eV)') if solid: plt.ylabel('Response (V/GW/sr)') else: plt.ylabel('Response (V/GW)') plt.title(title) plt.show() return
[docs]def plotTraces(channels, measurementFrame, scale='regular'): r""" Given a dataframe of Dante channel data, plot all the signal traces onto a single plot. channels: list, numpy.ndarray List or array of relevant channels measurementFrame: pandas.core.frame.DataFrame Pandas dataframe containing DANTE measurement data. See readDanteData() and readDanProcessed(). Parameters ---------- Returns ------- Notes ----- See also -------- Examples -------- """ if scale == 'regular': for channel in channels: plt.plot(measurementFrame['Time' + str(channel)], measurementFrame['Signal' + str(channel)], label=str(channel)) plt.legend(frameon=False, labelspacing=0.001, borderaxespad=0.1) plt.xlabel('Time (ns)') plt.ylabel('Signal (V)') plt.title('Dante Measurement Data') plt.show() elif scale == 'log': for channel in channels: plt.semilogy(measurementFrame['Time' + str(channel)], measurementFrame['Signal' + str(channel)], label=str(channel)) plt.legend(frameon=False, labelspacing=0.001, borderaxespad=0.1) plt.xlabel('Time (ns)') plt.ylabel('Signal (V)') plt.title('Dante Measurement Data') plt.show() else: raise Exception(f"No such method for scale: {scale}") return
def uniformStreak(times, energies, spectra): r""" Convert streak spectra from an irregularly spaced energy sampling to uniformly spaced energies via linear interpolation. This is primarily useful for visualizing the streaked spectrum in plotStreak(). times : numpy.ndarray 1D array of times corresponding to streaked spectrum energies : numpy.ndarray 2D array of energies which are nonuniformly spaced. Each column corresponds to the photon energy axis for a particularly time. Typically all the columns are identical. spectra : numpy.ndarray 2D array of streaked spectra corresponding to energies array. """ # generate a uniform energy axis energyMin = int(np.min(energies)) # eV energyMax = int(np.max(energies)) # eV energyStep = 1 # eV energyNew = np.arange(start=energyMin, stop=energyMax, step=energyStep) newShape = (len(energyNew), len(times)) energiesNew = np.zeros(newShape) spectraNew = np.zeros(newShape) for idx, time in enumerate(times): energyOld = energies[:, idx] spectrumOld = spectra[:, idx] spectrumNew = np.interp(x=energyNew, xp=energyOld, fp=spectrumOld) # storing values energiesNew[:, idx] = energyNew spectraNew[:, idx] = spectrumNew return energiesNew, spectraNew
[docs]def plotStreak(times, energies, spectra): r""" Plot streak of unfolded Dante spectra. See analyzeStreak(). Parameters ---------- times: numpy.ndarray Array of times for which the unfold was analyzed. energies: numpy.ndarray Array of photon energies corresponding to the unfolded spectra. spectra: numpy.ndarray The unfolded spectral intensities as a 2D array. See analyzeStreak(). Returns ------- Notes ----- See also -------- Examples -------- """ energiesInterp, spectraInterp = uniformStreak(times, energies, spectra) extent=[energiesInterp[0,0], energiesInterp[-1,0], times[0], times[-1]] plt.imshow(spectraInterp.transpose(), extent=extent, origin='lower', aspect='auto') plt.ylabel('Time (ns)') plt.xlabel('Photon Energy (eV)') cbar = plt.colorbar() cbar.set_label("Spectrum (GW/sr/eV)") plt.show() return
[docs]def signalImg(signalsArr): r""" Visualize dante signals as an image. Parameters ---------- signalsArr: numpy.ndarray Returns ------- Notes ----- See also -------- Examples -------- """ plt.imshow(signalsArr.transpose(), aspect='auto') plt.xlabel('Time') plt.ylabel('channel') cbar = plt.colorbar() cbar.set_label('Signal') plt.show() return
def individualChPlot(channels, timesFrame, signalsFrame, cut=None): r""" Plot individual channels on separate plots. Parameters ---------- Returns ------- Notes ----- See also -------- Examples -------- """ if cut: timesFrame = timesFrame.iloc[cut:-cut] signalsFrame = signalsFrame.iloc[cut:-cut] for chNum in channels: plt.plot(timesFrame[chNum] * 1e9, signalsFrame[chNum]) plt.title(f"Ch {chNum}") plt.xlabel("Time (ns)") plt.ylabel("Signal (V)") plt.show() return def traceGrid(channels, timesFrame, signalsFrame, chStatus=None, saveName="", cut=None, shotNum=""): r""" Plot individual channels on separate plots arranged into a grid. Parameters ---------- Returns ------- Notes ----- See also -------- Examples -------- """ # cutting leading and trailing edges of signal where noise exists if cut: timesFrame = timesFrame.iloc[cut:-cut] signalsFrame = signalsFrame.iloc[cut:-cut] # setting up subplots for all 18 channels fig, ax = plt.subplots(6, 3, sharex=True, sharey=True, figsize=(15,10)) fig.subplots_adjust(hspace=0, wspace=0) # creating array of all possible dante channels allCh = np.arange(18) + 1 # annotating each subplot window with the channel number for chNum in allCh: col = int((chNum - 1)/6) row = (chNum - 1) % 6 if not chStatus: ax[row][col].annotate(f'Ch {chNum}', xy=(0, 0.8), xytext=(0, 0.8)) else: ax[row][col].annotate(f'Ch {chNum} - {chStatus[chNum]}', xy=(0, 0.8), xytext=(0, 0.8)) # plotting traces for only user specified channels for chNum in channels: col = int((chNum - 1)/6) row = (chNum - 1) % 6 ax[row][col].plot(timesFrame[chNum] * 1e9, signalsFrame[chNum] / np.max(signalsFrame[chNum])) ax[row][col].set_ylim((-0.1, 1.1)) # ax[row][col].annotate(f'Ch {chNum}', xy=(0, 0.8), xytext=(0, 0.8)) fig.suptitle(f"Shot {shotNum}") fig.text(0.5, 0.04, 'Time (ns, arbitrary offset)', ha='center') fig.text(0.04, 0.5, 'Signal (normalized to ch max)', va='center', rotation='vertical') if saveName: # if there is a non-empty name, then try to save plt.savefig(saveName) else: # no name, then just show plt.show() return