From 1a8a65960b5b275a9d9f4d428523442d27918cf1 Mon Sep 17 00:00:00 2001 From: Craig Warren Date: Sat, 26 Nov 2022 16:52:36 +0000 Subject: [PATCH] Docstrings updates --- toolboxes/Plotting/README.rst | 18 ++++ toolboxes/Plotting/plot_Bscan.py | 19 +--- toolboxes/Plotting/plot_antenna_params.py | 122 ++++++++++++++-------- toolboxes/Plotting/plot_source_wave.py | 54 ++++++---- 4 files changed, 132 insertions(+), 81 deletions(-) create mode 100644 toolboxes/Plotting/README.rst diff --git a/toolboxes/Plotting/README.rst b/toolboxes/Plotting/README.rst new file mode 100644 index 00000000..06d373d6 --- /dev/null +++ b/toolboxes/Plotting/README.rst @@ -0,0 +1,18 @@ +Toolboxes is a sub-package where useful Python modules contributed by users are stored. + +******** +Plotting +******** + +Information +=========== + +This package is intended to provide some basic scripts to get started with plotting outputs from simulations. + +Package contents +================ + +* ``plot_antenna_params.py`` plots antenna parameters - incident, reflected and total voltages and currents; s11, (s21) and input impedance from an output file containing a transmission line source. +* ``plot_Ascan.py`` plots electric and magnetic fields and currents from all receiver points in the given output file. Each receiver point is plotted in a new figure window. +* ``plot_Bscan.py`` plots a B-scan (multiple A-scans) image. +* ``plot_source_wave.py`` plot built-in waveforms that can be used for sources. \ No newline at end of file diff --git a/toolboxes/Plotting/plot_Bscan.py b/toolboxes/Plotting/plot_Bscan.py index 979336df..11a5d7d8 100644 --- a/toolboxes/Plotting/plot_Bscan.py +++ b/toolboxes/Plotting/plot_Bscan.py @@ -30,7 +30,7 @@ logger = logging.getLogger(__name__) def mpl_plot(filename, outputdata, dt, rxnumber, rxcomponent, save=False): - """Creates a plot (with matplotlib) of the B-scan. + """Creates a plot of the B-scan. Args: filename: string of filename (including path) of output file. @@ -101,28 +101,19 @@ if __name__ == "__main__": logger.exception(f'No receivers found in {args.outputfile}') raise ValueError - rxsgather = [] for rx in range(1, nrx + 1): outputdata, dt = get_output_data(args.outputfile, rx, args.rx_component) - print(outputdata.shape) if args.gather: - rxsgather.append(outputdata) + if rx == 1: + rxsgather = outputdata + rxsgather = np.column_stack((rxsgather, outputdata)) else: plthandle = mpl_plot(args.outputfile, outputdata, dt, rx, args.rx_component, save=args.save) # Plot all receivers from single output file together if required if args.gather: - rxsgather = np.array(rxsgather).transpose() plthandle = mpl_plot(args.outputfile, rxsgather, dt, rx, args.rx_component, save=args.save) - if args.save: - # Save a PDF of the figure - plthandle.savefig(args.outputfile[:-3] + '.pdf', dpi=None, - format='pdf', bbox_inches='tight', pad_inches=0.1) - # # Save a PNG of the figure - # plthandle.savefig(args.outputfile[:-3] + '.png', dpi=150, - # format='png', bbox_inches='tight', pad_inches=0.1) - else: - plthandle.show() + plthandle.show() diff --git a/toolboxes/Plotting/plot_antenna_params.py b/toolboxes/Plotting/plot_antenna_params.py index 1d7d026c..5fd7ece0 100644 --- a/toolboxes/Plotting/plot_antenna_params.py +++ b/toolboxes/Plotting/plot_antenna_params.py @@ -33,14 +33,14 @@ def calculate_antenna_params(filename, tltxnumber=1, tlrxnumber=None, rxnumber=N and currents; s11, (s21) and input impedance. Args: - filename (string): Filename (including path) of output file. - tltxnumber (int): Transmitter antenna - transmission line number - tlrxnumber (int): Receiver antenna - transmission line number - rxnumber (int): Receiver antenna - output number - rxcomponent (str): Receiver antenna - output electric field component + filename: string of filename (including path) of output file. + tltxnumber: int for transmitter antenna - transmission line number + tlrxnumber: int for receiver antenna - transmission line number + rxnumber: int for receiver antenna - output number + rxcomponent: in for receiver antenna - output electric field component Returns: - antennaparams (dict): Antenna parameters. + antennaparams: dict of antenna parameters. """ # Open output file and read some attributes @@ -73,7 +73,8 @@ def calculate_antenna_params(filename, tltxnumber=1, tlrxnumber=None, rxnumber=N Vref = Vtotal - Vinc Iref = Itotal - Iinc - # If a receiver antenna is used (with a transmission line or receiver), get received voltage for s21 + # If a receiver antenna is used (with a transmission line or receiver), + # get received voltage for s21 if tlrxnumber: tlrxpath = '/tls/tl' + str(tlrxnumber) + '/' Vrec = f[tlrxpath + 'Vtotal'][:] @@ -83,7 +84,9 @@ def calculate_antenna_params(filename, tltxnumber=1, tlrxnumber=None, rxnumber=N availableoutputs = list(f[rxpath].keys()) if rxcomponent not in availableoutputs: - logger.exception(f"{rxcomponent} output requested, but the available output for receiver {rxnumber} is {', '.join(availableoutputs)}") + logger.exception(f"{rxcomponent} output requested, but the available " + + f"output for receiver {rxnumber} is " + + f"{', '.join(availableoutputs)}") raise ValueError rxpath += rxcomponent @@ -100,7 +103,8 @@ def calculate_antenna_params(filename, tltxnumber=1, tlrxnumber=None, rxnumber=N # Frequency bins freqs = np.fft.fftfreq(Vinc.size, d=dt) - # Delay correction - current lags voltage, so delay voltage to match current timestep + # Delay correction - current lags voltage, so delay voltage to match + # current timestep delaycorrection = np.exp(1j * 2 * np.pi * freqs * (dt / 2)) # Calculate s11 and (optionally) s21 @@ -134,9 +138,10 @@ def calculate_antenna_params(filename, tltxnumber=1, tlrxnumber=None, rxnumber=N s11[np.invert(np.isfinite(s11))] = 0 # Create dictionary of antenna parameters - antennaparams = {'time': time, 'freqs': freqs, 'Vinc': Vinc, 'Vincp': Vincp, 'Iinc': Iinc, 'Iincp': Iincp, - 'Vref': Vref, 'Vrefp': Vrefp, 'Iref': Iref, 'Irefp': Irefp, - 'Vtotal': Vtotal, 'Vtotalp': Vtotalp, 'Itotal': Itotal, 'Itotalp': Itotalp, + antennaparams = {'time': time, 'freqs': freqs, 'Vinc': Vinc, 'Vincp': Vincp, + 'Iinc': Iinc, 'Iincp': Iincp, 'Vref': Vref, 'Vrefp': Vrefp, + 'Iref': Iref, 'Irefp': Irefp, 'Vtotal': Vtotal, + 'Vtotalp': Vtotalp, 'Itotal': Itotal, 'Itotalp': Itotalp, 's11': s11, 'zin': zin, 'yin': yin} if tlrxnumber or rxnumber: with np.errstate(divide='ignore'): # Ignore warning from taking a log of any zero values @@ -147,22 +152,31 @@ def calculate_antenna_params(filename, tltxnumber=1, tlrxnumber=None, rxnumber=N return antennaparams -def mpl_plot(filename, time, freqs, Vinc, Vincp, Iinc, Iincp, Vref, Vrefp, Iref, Irefp, Vtotal, Vtotalp, Itotal, Itotalp, s11, zin, yin, s21=None): - """Plots antenna parameters - incident, reflected and total volatges and +def mpl_plot(filename, time, freqs, Vinc, Vincp, Iinc, Iincp, Vref, Vrefp, + Iref, Irefp, Vtotal, Vtotalp, Itotal, Itotalp, s11, zin, yin, + s21=None, save=False): + """Plots antenna parameters - incident, reflected and total voltages and currents; s11, (s21) and input impedance. Args: - filename (string): Filename (including path) of output file. - time (array): Simulation time. - freq (array): Frequencies for FFTs. - Vinc, Vincp, Iinc, Iincp (array): Time and frequency domain representations of incident voltage and current. - Vref, Vrefp, Iref, Irefp (array): Time and frequency domain representations of reflected voltage and current. - Vtotal, Vtotalp, Itotal, Itotalp (array): Time and frequency domain representations of total voltage and current. - s11, s21 (array): s11 and, optionally, s21 parameters. - zin, yin (array): Input impedance and input admittance parameters. + filename: string of filename (including path) of output file. + time: array of simulation time. + freq: array of frequencies for FFTs. + Vinc, Vincp, Iinc, Iincp: arrays of time and frequency domain + representations of incident voltage and + current. + Vref, Vrefp, Iref, Irefp: arrays of time and frequency domain + representations of reflected voltage and + current. + Vtotal, Vtotalp, Itotal, Itotalp: arrays of time and frequency domain + representations of total voltage and + current. + s11, s21: array(s) of s11 and, optionally, s21 parameters. + zin, yin: arrays of input impedance and input admittance parameters. + save: boolean flag to save plot to file. Returns: - plt (object): matplotlib plot object. + plt: matplotlib plot object. """ # Set plotting range @@ -175,9 +189,11 @@ def mpl_plot(filename, time, freqs, Vinc, Vincp, Iinc, Iincp, Vref, Vrefp, Iref, # Print some useful values from s11, and input impedance s11minfreq = np.where(s11[pltrange] == np.amin(s11[pltrange]))[0][0] - logger.info(f's11 minimum: {np.amin(s11[pltrange]):g} dB at {freqs[s11minfreq + pltrangemin]:g} Hz') + logger.info(f's11 minimum: {np.amin(s11[pltrange]):g} dB at ' + + f'{freqs[s11minfreq + pltrangemin]:g} Hz') logger.info(f'At {freqs[s11minfreq + pltrangemin]:g} Hz...') - logger.info(f'Input impedance: {np.abs(zin[s11minfreq + pltrangemin]):.1f}{zin[s11minfreq + pltrangemin].imag:+.1f}j Ohms') + logger.info(f'Input impedance: {np.abs(zin[s11minfreq + pltrangemin]):.1f}' + + f'{zin[s11minfreq + pltrangemin].imag:+.1f}j Ohms') # logger.info(f'Input admittance (mag): {np.abs(yin[s11minfreq + pltrangemin]):g} S') # logger.info(f'Input admittance (phase): {np.angle(yin[s11minfreq + pltrangemin], deg=True):.1f} deg') @@ -413,19 +429,21 @@ def mpl_plot(filename, time, freqs, Vinc, Vincp, Iinc, Iincp, Vref, Vrefp, Iref, # ax.set_ylim([-40, 100]) # ax.grid(which='both', axis='both', linestyle='-.') - # Save a PDF/PNG of the figure - savename1 = filename.stem + '_tl_params' - savename1 = filename.parent / savename1 - savename2 = filename.stem + '_ant_params' - savename2 = filename.parent / savename2 - # fig1.savefig(savename1.with_suffix('.png'), dpi=150, format='png', - # bbox_inches='tight', pad_inches=0.1) - # fig2.savefig(savename2.with_suffix('.png'), dpi=150, format='png', - # bbox_inches='tight', pad_inches=0.1) - # fig1.savefig(savename1.with_suffix('.pdf'), dpi=None, format='pdf', - # bbox_inches='tight', pad_inches=0.1) - # fig2.savefig(savename2.with_suffix('.pdf'), dpi=None, format='pdf', - # bbox_inches='tight', pad_inches=0.1) + if save: + savename1 = filename.stem + '_tl_params' + savename1 = filename.parent / savename1 + savename2 = filename.stem + '_ant_params' + savename2 = filename.parent / savename2 + # Save a PDF of the figure + fig1.savefig(savename1.with_suffix('.pdf'), dpi=None, format='pdf', + bbox_inches='tight', pad_inches=0.1) + fig2.savefig(savename2.with_suffix('.pdf'), dpi=None, format='pdf', + bbox_inches='tight', pad_inches=0.1) + # Save a PNG of the figure + # fig1.savefig(savename1.with_suffix('.png'), dpi=150, format='png', + # bbox_inches='tight', pad_inches=0.1) + # fig2.savefig(savename2.with_suffix('.png'), dpi=150, format='png', + # bbox_inches='tight', pad_inches=0.1) return plt @@ -433,14 +451,28 @@ def mpl_plot(filename, time, freqs, Vinc, Vincp, Iinc, Iincp, Vref, Vrefp, Iref, if __name__ == "__main__": # Parse command line arguments - parser = argparse.ArgumentParser(description='Plots antenna parameters (s11, s21 parameters and input impedance) from an output file containing a transmission line source.', usage='cd gprMax; python -m tools.plot_antenna_params outputfile') + parser = argparse.ArgumentParser(description='Plots antenna parameters - ' + + 'incident, reflected and total voltages ' + + 'and currents; s11, (s21) and input impedance ' + + 'from an output file containing a transmission ' + + 'line source.', + usage='cd gprMax; python -m toolboxes.Plotting.plot_antenna_params outputfile') parser.add_argument('outputfile', help='name of output file including path') - parser.add_argument('--tltx-num', default=1, type=int, help='transmitter antenna - transmission line number') - parser.add_argument('--tlrx-num', type=int, help='receiver antenna - transmission line number') - parser.add_argument('--rx-num', type=int, help='receiver antenna - output number') - parser.add_argument('--rx-component', type=str, help='receiver antenna - output electric field component', choices=['Ex', 'Ey', 'Ez']) + parser.add_argument('--tltx-num', default=1, type=int, + help='transmitter antenna - transmission line number') + parser.add_argument('--tlrx-num', type=int, + help='receiver antenna - transmission line number') + parser.add_argument('--rx-num', type=int, + help='receiver antenna - output number') + parser.add_argument('--rx-component', type=str, + help='receiver antenna - output electric field component', + choices=['Ex', 'Ey', 'Ez']) + parser.add_argument('-save', action='store_true', default=False, + help='save plot directly to file, i.e. do not display') args = parser.parse_args() - antennaparams = calculate_antenna_params(args.outputfile, args.tltx_num, args.tlrx_num, args.rx_num, args.rx_component) - plthandle = mpl_plot(args.outputfile, **antennaparams) + antennaparams = calculate_antenna_params(args.outputfile, args.tltx_num, + args.tlrx_num, args.rx_num, + args.rx_component) + plthandle = mpl_plot(args.outputfile, **antennaparams, save=args.save) plthandle.show() diff --git a/toolboxes/Plotting/plot_source_wave.py b/toolboxes/Plotting/plot_source_wave.py index 53fe663f..0ba3e392 100644 --- a/toolboxes/Plotting/plot_source_wave.py +++ b/toolboxes/Plotting/plot_source_wave.py @@ -32,12 +32,12 @@ def check_timewindow(timewindow, dt): """Checks and sets time window and number of iterations. Args: - timewindow (float): Time window. - dt (float): Time discretisation. + timewindow: float of time window. + dt: flost of time discretisation. Returns: - timewindow (float): Time window. - iterations (int): Number of interations. + timewindow: float of time window. + iterations: int of number of interations. """ # Time window could be a string, float or int, so convert to string then check @@ -59,18 +59,19 @@ def check_timewindow(timewindow, dt): return timewindow, iterations -def mpl_plot(w, timewindow, dt, iterations, fft=False): +def mpl_plot(w, timewindow, dt, iterations, fft=False, save=False): """Plots waveform and prints useful information about its properties. Args: - w (class): Waveform class instance. - timewindow (float): Time window. - dt (float): Time discretisation. - iterations (int): Number of iterations. - fft (boolean): Plot FFT switch. + w: Waveform class instance. + timewindow: float of time window. + dt: float of time discretisation. + iterations: int of number of iterations. + fft: boolean flag to plot FFT. + save: boolean flag to save plot to file. Returns: - plt (object): matplotlib plot object. + plt: matplotlib plot object. """ time = np.linspace(0, (iterations - 1) * dt, num=iterations) @@ -143,12 +144,14 @@ def mpl_plot(w, timewindow, dt, iterations, fft=False): # Turn on grid [ax.grid(which='both', axis='both', linestyle='-.') for ax in fig.axes] - # Save a PDF/PNG of the figure - savefile = Path(__file__).parent / w.type - # fig.savefig(savefile.with_suffix('.pdf'), dpi=None, format='pdf', - # bbox_inches='tight', pad_inches=0.1) - # fig.savefig(savefile.with_suffix('.png'), dpi=150, format='png', - # bbox_inches='tight', pad_inches=0.1) + if save: + savefile = Path(__file__).parent / w.type + # Save a PDF of the figure + fig.savefig(savefile.with_suffix('.pdf'), dpi=None, format='pdf', + bbox_inches='tight', pad_inches=0.1) + # Save a PNG of the figure + # fig.savefig(savefile.with_suffix('.png'), dpi=150, format='png', + # bbox_inches='tight', pad_inches=0.1) return plt @@ -156,21 +159,27 @@ def mpl_plot(w, timewindow, dt, iterations, fft=False): if __name__ == "__main__": # Parse command line arguments - parser = argparse.ArgumentParser(description='Plot built-in waveforms that can be used for sources.', usage='cd gprMax; python -m tools.plot_source_wave type amp freq timewindow dt') + parser = argparse.ArgumentParser(description='Plot built-in waveforms that can be used for sources.', + usage='cd gprMax; python -m toolboxes.Plotting.plot_source_wave type amp freq timewindow dt') parser.add_argument('type', help='type of waveform', choices=Waveform.types) parser.add_argument('amp', type=float, help='amplitude of waveform') parser.add_argument('freq', type=float, help='centre frequency of waveform') parser.add_argument('timewindow', help='time window to view waveform') parser.add_argument('dt', type=float, help='time step to view waveform') - parser.add_argument('-fft', action='store_true', help='plot FFT of waveform', default=False) + parser.add_argument('-fft', action='store_true', default=False, + help='plot FFT of waveform') + parser.add_argument('-save', action='store_true', default=False, + help='save plot directly to file, i.e. do not display') args = parser.parse_args() # Check waveform parameters if args.type.lower() not in Waveform.types: - logger.exception(f"The waveform must have one of the following types {', '.join(Waveform.types)}") + logger.exception(f"The waveform must have one of the following types " + + f"{', '.join(Waveform.types)}") raise ValueError if args.freq <= 0: - logger.exception('The waveform requires an excitation frequency value of greater than zero') + logger.exception('The waveform requires an excitation frequency value of ' + + 'greater than zero') raise ValueError # Create waveform instance @@ -180,5 +189,6 @@ if __name__ == "__main__": w.freq = args.freq timewindow, iterations = check_timewindow(args.timewindow, args.dt) - plthandle = mpl_plot(w, timewindow, args.dt, iterations, args.fft) + plthandle = mpl_plot(w, timewindow, args.dt, iterations, fft=args.fft, + save=args.save) plthandle.show()