BRIXS is an object-oriented (OO) python package for processing/analysis of XAS and RIXS spectra.
Click here https://cwgaldino.github.io/brixs/ for brixs documentation.
Object-oriented is a programming model that organizes software design around objects, rather than functions. An OO approach makes sense for data processing/analysis of XAS and RIXS because it allows more intuitive syntax and make the project easier to maintain and upgrade as it grows.
At the lab (beamline), the output of an experiment is often a data file which is saved on a computer. We wish to open this file, perform some operations (processing), and display it in a meaningful way. Below we can see some typical examples of everyday data processing and how OO can facilitate data processing.
Suppose that our dataset is composed of two data arrays (xy-pair), where y=f(x). Each dataset also have a temperature (T) and pressure (P) associated to it. A typical script for reading, operation, and displaying one dataset from a file would look like this:
# import packages
from mypackage import read_data
import matplotlib.pyplot as plt
import numpy as np
import scipy
# import data
x, y, T, P = read_data(...)
# initial processing
x = x + 10 # shift the x-axis
y = y * 2.1 # apply a multiplicative factor
# interpolating data
x_new = np.arange(0, 100, 1000) # define a more suitable x-axis
x = np.interp(x_new, x, y) # interpolate data to the new x-axis
# fit data with a gaussian peak
gaussian = lambda x, mu, sig: 1.0/(np.sqrt(2.0*np.pi)*sig)*np.exp(-np.power((x-mu)/sig, 2.0)/2)
guess = [max(y), x[argmax(y)], x[argmax(y)]*0.1]
popt, _ = scipy.optimize.curve_fit(gaussian, x, y, p0=guess)
# display data
plt.figure() # open new figure
plt.plot(x, y, label=f"T={T}, P={P}") # plot data
plt.plot(x, gaussian(x, popt*)) # plot fitting
plt.legend() # legend
plt.show() # show figure
In an OO approach, the same processing would look like this,
# import packages
import brixs as br
# import data
s = br.Spectrum(...)
# initial processing
s.shift = 10 # shift the x-axis
s.factor = 2.1 # apply a multiplicative factor
s.interp(0, 10, 1000) # interpolate data
s.fit_peak() # fit data with a gaussian peak
# display data
br.figure() # open new figure
s.plot(label=f"T={s.T}, P={s.P}") # plot data
s.model.plot() # plot fitting
br.leg() # legend
plt.show() # show figure
We can argue that keeping track and labeling data is more intuitive in an OO approach as the number of variables is drastically reduced. For instance, if one tries to load 2 different datasets we have 2 variables with OO vs 8 variables using a functional approach:
# dataset number 1 and 2
s1 = br.Spectrum(...)
s2 = br.Spectrum(...)
# x and y arrays as well values of temperature and pressure are store inside the object
print('x1', s1.x)
print('T1', s1.T)
where in a functional approach, the number of variables can easily start to became overwhelming
# dataset number 1 and 2
x1, y1, T1, P1 = read_data(...)
x2, y2, T2, P2 = read_data(...)
At the same time, OO does not limit the most experienced users, because you can always simulate a functional approach by extracting the x and y data from the object like,
s1 = br.Spectrum(...)
x = s.x
y = s.y
also new metadata can be added on the fly,
s.angle = 12.53
Just like metadata, repetitive tasks can be added to the object,
# define new method
def common_processing(s):
s.shift = 10
s.factor = 2.1
s.interp(0, 10, 1000)
s.fit_peak()
# add new method to all Spectrum objects
br.Spectrum.processing = common_processing
# from now on, one can use new method on every Spectrum object
s1.processing()
s2.processing()
BRIXS is based on four major objects:
im = br.Image()
pe = br.PhotonEvents()
s = br.Spectrum()
ss = br.Spectra()
The Image object is used for handling 2D arrays, like detector images. For detectors capable of single photon count, one can use a centroid algorithm to get a sub-pixel resolution. The output a a centroid algorithm is a photon events list, which is handled by the PhotonEvents object. Either way, the detector data is eventually turned into a spectrum which is handled by the Spectrum object. This is the most rich object of the BRIXS package so far. Batch operation, data alignment, or any data manipulation that requires comparison between many spectra can be done via the Spectra object. Having only four classes makes the code easy to maintain. A better description of each object will be given later in this readme.
BRIXS also comes with additional functionally from supporting modules.
Backpack is a module with quality-of-life (QOL) functions. This module is completely independent from brixs. As for the time of writing, these are the submodules:
brixs.figmanip # matplotlib QOL functions
brixs.filemanip # file reading and saving QOL functions
brixs.arraymanip # array manipulation QOL functions
brixs.numanip # float/int manipulation QOL functions
brixs.interact # user interaction QOL functions
See the documentation for a description of the functions available. All functions within backpack are readily available when brixs is imported. For instance, the function brixs.arraymanip.check_monotonicity which checks the monotonicity of an array can be called directly from brixs:
# import brixs
import brixs as br
# array
a = [1, 2, 3, 4, 5, 6]
# check monotonicity
br.check_monotonicity(a)
Module for quickly saving and recovering processed spectra so you can avoid running functions multiple types with same input parameters. Finder is imported with brixs
# import brixs
import brixs as br
# set finder folderpath
br.finder.folderpath = '<folderpath>'
# apply the decorator to you function
@br.finder.track
def processing_function(a, b, c):
s = <does something with a, b and c and returns s>
return s
# processing may take a while if it is the first time you run
s = processing_function(a=1, b=2, c=3)
# if you run processing with same parameters, it runs instantly because
# finder recovers already processed spectrum
s = processing_function(a=1, b=2, c=3)
Full description of finder functionally can be found inside brixs.addons.finder.py file.
Module with common x and y labels for xas and rixs plots.
# this
br.labels.xas()
# is the same as this
plt.xlabel('Photon Energy (eV)')
plt.ylabel('Intensity')
Full description of labels can be found inside brixs.addons.labels.py file.
Module for data fitting. For enabling fitting functionally do
# enable fitting functionality
import brixs.model
# model functions are then available
br.model.gaussian()
This module is not ready yet.
BRIXS objects and modules are independent of data collection methods and data file types. All "file reading" functions specific for each lab or beamline which reads a file and converts it to one of the 4 major BRIXS objects can be found in the beamlines folder.
For example, data collected at I21 beamline of Diamond Light Source, can use imported as a Image object using the code below,
# method 1
import brixs.beamlines.I21 as I21
im = I21.read(<filepath>)
# method 2
from brixs.beamlines.I21 import read
im = read(<filepath>)
Module with function for calculating momentum transfer in single crystals. It is assumed that the photon hits the crystal surface at a angle th and is scattered in a 2th angle. See drawing inside brixs.crystal.crystal.py file for more information. This module can be used like this
# import
import brixs.crystal
# functions available
br.ev2angstrom()
br.calculate_q_transfer()
br.momentum2rlu()
The description of each function can be accessed via the python help() function or by reading the documentation (PUT LINK HERE).
Module for spreadsheet manipulation [FUTURE]. This is not ready yet.
There are two recommended methods:
- Using pip
pip install git+https://github.com/cwgaldino/brixs
or
- Cloning (or downloading) the GitHub repository then adding brixs to the "path":
import sys
sys.path.append('<path-to-brixs>')
import brixs as br
- numpy
- matplotlib
Some modules require additional imports:
- scipy
- lmfit
- pbcpy
Some modules here might require
- h5py
Refer to the examples folder on GitHub for code examples.