-
Notifications
You must be signed in to change notification settings - Fork 16
Features (peaks) and Energy Cal explanation and diagrams
I'll explain this as if to myself from 2 weeks ago.
For single inheritance, super()
is pretty simple. It allows you to call methods of the parent class, with directly specifying the parent class. This is a handy shortcut but that's about it.
But for multiple inheritance, super()
is very important. If your class has multiple parents, you want to be able to call methods (particularly __init__
) from every parent class, not just one. You do this by putting a super()
call in every __init__
method of the classes involved. The key thing to understand is that super()
does not go up to the parent class of whatever class it is in; it goes to the next class in the method resolution order (MRO). By putting super()
in every __init__
, it ensures that all the __init__
s get called, even if this is not required for the class in which you are writing super()
. It might be necessary for a daughter of that class. Otherwise, when the daughter calls super().__init__()
, it only calls the first parent class, and could stop there.
This article explains it much better.
Also note that I from builtins import super
which uses the future
package to provide Python 3 super
functionality even in Python 2.
A peak in a spectrum has some fundamental properties, like energy and net counts (area). However, more generally one can have a feature in a spectrum that is not a peak, but still has a characteristic energy and/or area. For example a Compton edge has an energy but possibly not an area. So the base class is called Feature
, and is an abstract class because it isn't a concrete thing yet.
Characteristics of a feature such as energy and area each carry certain functionality. For example, if you have a feature with energy, you can assign a calibration energy -- the known energy value of that feature -- in addition to the energy value (in channels) calculated from the spectrum. This functionality, which is based on a certain property of the feature, is contained in EnergyFeature
which inherits from Feature
. Similarly for AreaFeature
, which would be used for an efficiency calibration (not implemented yet). Another subclass is SpectralFeature
which contains the functionality of including a Spectrum
object. By including the Spectrum
in the attributes of the feature, the methods of the feature (such as peak-fitting) have access to the spectral data. The spectrum could be an intrinsic part of the Feature
base class, however, it is convenient for calibration to have simple features with only a channel/energy value pair, without requiring an associated spectrum.
EnergyFeature
, AreaFeature
, and SpectralFeature
are all abstract classes that inherit from Feature
. They cannot be concrete yet, because they do not provide the means by which the energy or area is calculated.
The abstract classes are the building blocks for concrete feature classes. The concrete classes don't need to rewrite the functions of the abstract classes, because everything is handled using super()
(see separate discussion).
A simple example is the ArbitraryEnergyPoint
, which inherits from EnergyFeature
and just takes a channel and energy value and assigns them to the appropriate properties of EnergyFeature
.
A more complete example is GrossROIPeak
, which isn't very useful, but is just a usage example. GrossROIPeak
is a feature associated with a spectrum, that has an area (gross counts) as well as an energy (centroid energy). So it inherits from SpectralFeature
, EnergyFeature
, and AreaFeature
. GrossROIPeak
calls the __init__
methods of all its parent classes (through the magic of super()
). It provides its own methods that simply define how you initialize it (with a region of interest), and how you compute energy and area. The rest of the interface (calibration energy or area, spectrum input, providing properties) is handled by the parents.
There are two kinds of energy calibration. The simplest kind is just an equation that converts channels to energies. The only data associated with it are coefficients (e.g. for a polynomial). It might come from a SPE file.
The second kind of energy calibration is not only an equation, but also a set of calibration points which produced the calibration. In this code, the points are Feature objects (actually, they must be subclasses of EnergyFeature
). Since the points are included with the calibration object, you can add new points and calculate a new best fit, or revise or remove calibration points. You also want to weight points based on the uncertainty of the energy values (not implemented yet).
I designed the class structure of energy calibration to allow both of these styles to be used, while minimizing duplicated code.
EnergyCalBase
is the abstract base class for all energy calibrations, as the name implies. It requires subclasses to have a ch2kev
method, which converts channels to keV. (kev2ch might be required too.)
FitEnergyCalBase
is the abstract base class for the "second kind" of energy calibration described above, with a set of calibration points that can be adjusted. It is initialized with a list of Feature objects (the calibration points), has methods for adding and removing calibration points, and requires a fit
method which uses the features to produce the coefficients or curve for ch2kev
. It cannot be concrete because it does not define a fit
method.
SimplePolyCal
is the "first kind" of energy calibration described above. It represents simply a polynomial equation for converting channels to keV. It inherits from EnergyCalBase
only and takes coefficients as inputs.
FitPolyCal
is the "second kind" of energy calibration described above. It inherits from FitEnergyCalBase
and SimplePolyCal
. It starts with the list of features/peaks, and fits a polynomial curve of the order specified. The actual calibration curve is produced with the SimplePolyCal
code using the coefficients from the fit, so ch2kev
is defined in SimplePolyCal
instead of being written again.
FitPolyCal
inheriting from SimplePolyCal
complicates things, because you have to make sure you fit the points to get coefficients before you can run SimplePolyCal
's __init__
method. There may be a cleaner way to arrange the super()
calls and __init__
s, or it could be done without inheriting from SimplePolyCal
. That inheritance only saves a couple lines of code.