Source code for fitlistreader.fitlistreader

import sys

from .fitlist import Fitlist
from .peak import Peak
from .fitintegral import FitIntegral

usefile_path = f"fitlistreader"
helptext = f"""
Displays information saved in fitlist.xml files as generated from HDTV. 

Usage: $ {usefile_path} [-h] [-I] [-C] [--noheader] [-l args] path/to/fitlist.xml

Options:
-I --Integrals       Switches to integral mode. Only the recorded integrals are listed. 
                      This is different from the int_* options for -l, as all integrals are listed 
                      and not only the ones belonging to peaks.
-C --Calibrations    Switches to read out the calibrations of respective spectra contained in the fitlist
-h --help            Displays this help text and exits. 
   --noheader        Ommit printing the header line
-l --labels [args]   Change printed values to those specified in labels arg (see below)


Labels:	
With the -l/--labels option, the values which are extracted from the fitlist.xml file can be changed. 
The values  will be printed in order of specification. Available labels are:
Label       Meaning
pos         Peak position
dpos        Uncertainty in peak position
vol         Peak volume
dvol        Uncertainty in peak volume
width       Peak width
dwidth      Uncertainty in peak width
bg          Background volume under the peak
dbg         Uncertainty in background volume under peak
bg_model    Background model used in fitting
spc         Spectrum name from which the peaks were fitted
cal         Calibration coefficients (polynomial, 0 order first) of the spectrum 
int_id      ID of corresponding Integral
int_pos     Position of corresponding Integral
int_dpos    Uncertainty in position of corresponding Integral
int_vol     Volume of corresponding Integral
int_dvol    Uncertainty in volume of corresponding Integral
int_width   Width of corresponding Integral
int_dwidth  Uncertainty in width of corresponding Integral
int_skew    Skew of corresponding Integral
int_dskew   Uncertainty in skew of corresponding Integral
"""

fmt_name = {
			"pos": " Position", 
			"dpos": " Δ Pos",
			"vol": " Volume",
			"dvol": " Δ Vol",
			"width": "  Width",
			"dwidth": "  Δ Wid",
			"bg": " Background",
			"dbg": "Δ BG",
			"bg_model": "\bBG Model", # highly cursed, but makes it line up almost every time!
			"spc": " Spectrum",
			"cal": " Calibration Coeffs",
			"int_id": "Int. ID",
			"int_pos": "Int. Pos",
			"int_dpos": "Int. Δ Pos",
			"int_vol": "Int. Vol",
			"int_dvol": "Int. Δ Vol",
			"int_width": "Int. Width",
			"int_dwidth": "Int. Δ Wid",
			"int_skew": "Int. Skew",
			"int_dskew": "Int. Δ Skew",
			"int_type": "Int. Type"
		}

[docs] def get_fmt_val_from_key(fitlist:Fitlist, peak:Peak, key:str, _fint:FitIntegral=None)->str: """Get formatted string representation of a named value from a fitlist. Returns a formatted string, tuned for printing tables, of a requested value from a specific Peak in a fitlist. When printing values from a fitlist, these values can have different datatypes and format requirements when printing them in a fixed space to the terminal. This function returns a string representation of a named value in an appropriate and human-readable format. Parameters ---------- fitlist : Fitlist The `Fitlist` object containing the requested peak and value. Needed to link to the `FitIntegralCollection` peak : Peak `Peak` object containing the requested value key : string Identifier for which value to retrieve. See `fmt_name.keys()` to get valid keys, or the help text for their meaning _fint : FitIntegral Instance of the valid `FitIntegral`for the queried value. Needed for certain 'int_*' keys. Will be inferred via fitlist if not given. Caution: Implicitly skips checking for correctness regarding the linking with the peak via the fitlist if supplied. """ if key == "pos": return f"{peak.pos : 4.2f}" elif key == "dpos": return f"{peak.dpos : 4.2f}" elif key == "vol": return f"{peak.vol : 4.2e}" elif key == "dvol": return f"{peak.dvol : 4.2e}" elif key == "width": c = f"{peak.width : 4.2f}" return f"{c: >6}" elif key == "dwidth": c = f"{peak.dwidth : 4.2f}" return f"{c: >6}" elif key == "bg": return f"{peak.bg.vol : 4.2e}" elif key == "dbg": return f"{peak.bg.dvol : 4.2e}" elif key == "bg_model": return f"{peak.bg.model[:4]} deg:{peak.bg.nparams :1d}" elif key == "spc": return f"{peak.spc_name}" elif key == "cal": lst = peak.calibration.split() if not lst: return "" s = [] for v in lst: s.append(f"{float(v): 2.1e},") s[-1] = s[-1][:-1] return ''.join(s) # for integral properties, we first have to retrieve the integral object (if not given by internal argument _fint) # this needs to be done via the peakID. We take the 'sub' integral by default and fall back to the 'tot' integral if sub doesn't exist. elif key == "int_id": return f"{peak.ID: 03.0f}" elif key == "int_pos": if _fint is None: _fint = fitlist.get_integral_by_peakID(peak.ID)["sub"] or fitlist.get_integral_by_peakID(peak.ID)["tot"] return f"{_fint.pos: 4.2f}" elif key == "int_dpos": if _fint is None: _fint = fitlist.get_integral_by_peakID(peak.ID)["sub"] or fitlist.get_integral_by_peakID(peak.ID)["tot"] return f"{_fint.dpos: 4.2f}" elif key == "int_vol": if _fint is None: _fint = fitlist.get_integral_by_peakID(peak.ID)["sub"] or fitlist.get_integral_by_peakID(peak.ID)["tot"] return f"{_fint.vol: 4.2e}" elif key == "int_dvol": if _fint is None: _fint = fitlist.get_integral_by_peakID(peak.ID)["sub"] or fitlist.get_integral_by_peakID(peak.ID)["tot"] return f"{_fint.dvol: 4.2e}" elif key == "int_width": if _fint is None: _fint = fitlist.get_integral_by_peakID(peak.ID)["sub"] or fitlist.get_integral_by_peakID(peak.ID)["tot"] return f"{_fint.width: 4.2e}" elif key == "int_dwidth": if _fint is None: _fint = fitlist.get_integral_by_peakID(peak.ID)["sub"] or fitlist.get_integral_by_peakID(peak.ID)["tot"] return f"{_fint.dwidth: 4.2e}" elif key == "int_skew": if _fint is None: _fint = fitlist.get_integral_by_peakID(peak.ID)["sub"] or fitlist.get_integral_by_peakID(peak.ID)["tot"] return f"{_fint.skew: 4.2e}" elif key == "int_dskew": if _fint is None: _fint = fitlist.get_integral_by_peakID(peak.ID)["sub"] or fitlist.get_integral_by_peakID(peak.ID)["tot"] return f"{_fint.dskew: 4.2e}" elif key == "int_type": if _fint is not None: return " None" return " sub" if "sub" in fitlist.get_integral_by_peakID(peak.ID).keys() else " tot" else: raise RuntimeError(f"Supplied Key '{key}' is not a valid identifier of a named value")
[docs] def integral_printer(fitlist:Fitlist, int_type:str, labels:list, iter_labels:list, sep_len:int)->None: """Prints out all stored integrals of a given type for a supplied fitlist, formatted as a table. Prints Integral information to std_out. Available types of integral are 'tot' for total integrated area, bg for the area of the background in tot, and 'sub' for the subtracted rest of the volume. The values to be extracted can be specified in iter_labels, labels are what are printed in the header to describe the values. Only prints header when allowed by CMD line option. Separation between entries is always at least the minimum sufficient amount, but can be increased with sep_len. Parameters ---------- fitlist : Fitlist The `Fitlist` object containing the requested integrals. int_type : str Type of integrals to extract. Possible: 'tot', 'bg' or 'sub' labels : list[str] List of strings describing the requested values. Printed in header. iter_labels : list[str] List of keys with which to retrieve the actual values. See `get_fmt_val_from_key` sep_len : int Additional separation between table columns. """ len_lim = print_header(labels, sep_len) for fint, fid in fitlist.get_integral_list_by_type(int_type, include_ids=True): s = [f" {str(fid):<{len_lim}}"] for label in iter_labels[1:]: s.append(f"{get_fmt_val_from_key(fitlist, None, label, _fint=fint):<{len_lim}}") print(''.join(s))
[docs] def run()->None: """Method called by CLI to print requested information from a fitlist file to std_out, using `sys.argv`. """ if len(sys.argv)<2 or "-h" in sys.argv or "--help" in sys.argv: print(helptext) sys.exit(0) fl = Fitlist(sys.argv[-1]) do_print_header = "--noheader" not in sys.argv if "-C" in sys.argv or "--Calibrations" in sys.argv: # collect all calibrations for every spectrum in a dict, with the key equal to the spc name and the value equal a list of all saved calibrations coll = {} maxlen = [15, 10] for peak in fl: if len(peak.spc_name)>maxlen[0]+2: maxlen[0] = len(peak.spc_name)+2 if len(peak.calibration)>maxlen[1]+2: maxlen[1] = len(peak.calibration)+2 if peak.spc_name not in coll.keys(): coll[peak.spc_name] = [peak.calibration] elif peak.calibration not in coll[peak.spc_name]: # if different peaks have been fit with differing calibrations coll[peak.spc_name].append(peak.calibration) # print a name <-> calibration pair for every CALIBRATION (name might not be sufficient for multiple calibrations) if do_print_header: print(f"{'spectrum name':<{maxlen[0]}} {'calibration':<{maxlen[1]}}") for key, val in coll.items(): for v in val: print(f"{key:<{maxlen[0]}} {v:<{maxlen[1]}}") elif "-I" in sys.argv or "--Integrals" in sys.argv: # determine printed labels labels = ["Int. ID", " Pos", " Δ Pos", " Vol", " Δ Vol", " Width", " Δ Wid", " Skew", " Δ Skew"] iter_labels = ["int_id", "int_pos", "int_dpos", "int_vol", "int_dvol", "int_width", "int_dwidth", "int_skew", "int_dskew"] sep_len = 8 # print to console if do_print_header: print("\nTotal Integrals:") integral_printer(fl, "tot", labels, iter_labels, sep_len) if do_print_header: print("\nBackground Integrals:") integral_printer(fl, "bg", labels, iter_labels, sep_len) if do_print_header: print("\nSubtracted Integrals:") integral_printer(fl, "sub", labels, iter_labels, sep_len) else: # determine printed labels if "-l" in sys.argv or "--labels" in sys.argv: labels = [] iter_labels = [] ini = sys.argv.index("-l")+1 or sys.argv.index("--labels")+1 while ini<len(sys.argv)-1 and not sys.argv[ini].startswith('-'): try: labels.append(fmt_name[sys.argv[ini]]) iter_labels.append(sys.argv[ini]) ini += 1 except KeyError: print(f"Invalid label '{sys.argv[ini]}' encountered in '-l' option! Refer to help page to display all valid keys.") sys.exit(-1) if not labels: print(f"At least one valid label has to be specified when using the '-l' option.") sys.exit(1) else: labels = [" Position", " Δ Pos", " Volume", " Δ Vol", " Width", " Δ Wid", " Background", "Δ BG", "\bBG Model"] iter_labels = ["pos", "dpos", "vol", "dvol", "width", "dwidth", "bg", "dbg", "bg_model"] # determine table spacing for print-out sep_len = 8 len_lim = print_header(labels, sep_len) for peak in fl: s = [] for label in iter_labels: s.append(f"{get_fmt_val_from_key(fl, peak, label):<{len_lim}}") print(''.join(s))
if __name__ == "__main__": usefile_path = f"python3 {sys.argv[0]}" run()